diff options
Diffstat (limited to 'platform/node')
-rw-r--r-- | platform/node/CHANGELOG.md | 3 | ||||
-rw-r--r-- | platform/node/src/node_feature.cpp | 6 | ||||
-rw-r--r-- | platform/node/src/node_map.cpp | 221 | ||||
-rw-r--r-- | platform/node/src/node_map.hpp | 4 | ||||
-rw-r--r-- | platform/node/test/ignores.json | 15 | ||||
-rw-r--r-- | platform/node/test/js/map.test.js | 3 | ||||
-rw-r--r-- | platform/node/test/suite_implementation.js | 6 |
7 files changed, 243 insertions, 15 deletions
diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 486dd44e10..84fbff741c 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,3 +1,6 @@ +# master +* Add support for feature state APIs. ([#15480](https://github.com/mapbox/mapbox-gl-native/pull/15480)) + # 4.3.0 * Introduce `text-writing-mode` layout property for symbol layer ([#14932](https://github.com/mapbox/mapbox-gl-native/pull/14932)). The `text-writing-mode` layout property allows control over symbol's preferred writing mode. The new property value is an array, whose values are enumeration values from a ( `horizontal` | `vertical` ) set. * Fixed rendering and collision detection issues with using `text-variable-anchor` and `icon-text-fit` properties on the same layer ([#15367](https://github.com/mapbox/mapbox-gl-native/pull/15367)). diff --git a/platform/node/src/node_feature.cpp b/platform/node/src/node_feature.cpp index 2dfab686a7..646cc23338 100644 --- a/platform/node/src/node_feature.cpp +++ b/platform/node/src/node_feature.cpp @@ -167,6 +167,12 @@ v8::Local<v8::Object> toJS(const Feature& feature) { Nan::Set(result, Nan::New("id").ToLocalChecked(), FeatureIdentifier::visit(feature.id, ToValue())); } + Nan::Set(result, Nan::New("source").ToLocalChecked(), toJS(feature.source)); + if (!feature.sourceLayer.empty()) { + Nan::Set(result, Nan::New("sourceLayer").ToLocalChecked(), toJS(feature.sourceLayer)); + } + Nan::Set(result, Nan::New("state").ToLocalChecked(), toJS(feature.state)); + return scope.Escape(result); } diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 641816dc00..7450f461f8 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -117,6 +117,9 @@ ParseError)JS").ToLocalChecked()).ToLocalChecked(); Nan::SetPrototypeMethod(tpl, "setAxonometric", SetAxonometric); Nan::SetPrototypeMethod(tpl, "setXSkew", SetXSkew); Nan::SetPrototypeMethod(tpl, "setYSkew", SetYSkew); + Nan::SetPrototypeMethod(tpl, "setFeatureState", SetFeatureState); + Nan::SetPrototypeMethod(tpl, "getFeatureState", GetFeatureState); + Nan::SetPrototypeMethod(tpl, "removeFeatureState", RemoveFeatureState); Nan::SetPrototypeMethod(tpl, "dumpDebugLogs", DumpDebugLogs); Nan::SetPrototypeMethod(tpl, "queryRenderedFeatures", QueryRenderedFeatures); @@ -1099,6 +1102,224 @@ void NodeMap::SetYSkew(const Nan::FunctionCallbackInfo<v8::Value>& info) { info.GetReturnValue().SetUndefined(); } +void NodeMap::SetFeatureState(const Nan::FunctionCallbackInfo<v8::Value>& info) { + using namespace mbgl; + using namespace mbgl::style::conversion; + + auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() < 2) { + return Nan::ThrowTypeError("Two arguments required"); + } + + if (!info[0]->IsObject() || !info[1]->IsObject()) { + return Nan::ThrowTypeError("Both arguments must be objects"); + } + + std::string sourceID, featureID; + mbgl::optional<std::string> sourceLayerID; + auto feature = Nan::To<v8::Object>(info[0]).ToLocalChecked(); + if (Nan::Has(feature, Nan::New("source").ToLocalChecked()).FromJust()) { + auto sourceOption = Nan::Get(feature, Nan::New("source").ToLocalChecked()).ToLocalChecked(); + if (!sourceOption->IsString()) { + return Nan::ThrowTypeError("Requires feature.source property to be a string"); + } + sourceID = *Nan::Utf8String(sourceOption); + } else { + return Nan::ThrowTypeError("SetFeatureState: Requires feature.source property"); + } + + if (Nan::Has(feature, Nan::New("sourceLayer").ToLocalChecked()).FromJust()) { + auto sourceLayerOption = Nan::Get(feature, Nan::New("sourceLayer").ToLocalChecked()).ToLocalChecked(); + if (!sourceLayerOption->IsString()) { + return Nan::ThrowTypeError("SetFeatureState: Requires feature.sourceLayer property to be a string"); + } + sourceLayerID = {*Nan::Utf8String(sourceLayerOption)}; + } + + if (Nan::Has(feature, Nan::New("id").ToLocalChecked()).FromJust()) { + auto idOption = Nan::Get(feature, Nan::New("id").ToLocalChecked()).ToLocalChecked(); + if (!idOption->IsString() && !(idOption->IsNumber() || idOption->IsString())) { + return Nan::ThrowTypeError("Requires feature.id property to be a string or a number"); + } + featureID = *Nan::Utf8String(idOption); + } else { + return Nan::ThrowTypeError("SetFeatureState: Requires feature.id property"); + } + + Convertible state(info[1]); + + if (!isObject(state)) { + return Nan::ThrowTypeError("Feature state must be an object"); + } + + std::string sourceLayer = sourceLayerID.value_or(std::string()); + std::string stateKey; + Value stateValue; + bool valueParsed = false; + FeatureState newState; + + const std::function<optional<Error>(const std::string&, const Convertible&)> convertFn = + [&](const std::string& k, const Convertible& v) -> optional<Error> { + optional<Value> value = toValue(v); + if (value) { + stateValue = std::move(*value); + valueParsed = true; + } else if (isArray(v)) { + std::vector<Value> array; + std::size_t length = arrayLength(v); + array.reserve(length); + for (size_t i = 0; i < length; ++i) { + optional<Value> arrayVal = toValue(arrayMember(v, i)); + if (arrayVal) { + array.emplace_back(*arrayVal); + } + } + std::unordered_map<std::string, Value> result; + result[k] = std::move(array); + stateValue = std::move(result); + valueParsed = true; + return {}; + + } else if (isObject(v)) { + eachMember(v, convertFn); + } + if (!valueParsed) { + Nan::ThrowTypeError("Could not get feature state value"); + return nullopt; + } + stateKey = k; + newState[stateKey] = stateValue; + return nullopt; + }; + + eachMember(state, convertFn); + + try { + nodeMap->frontend->getRenderer()->setFeatureState(sourceID, sourceLayerID, featureID, newState); + } catch (const std::exception& ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().SetUndefined(); +} + +void NodeMap::GetFeatureState(const Nan::FunctionCallbackInfo<v8::Value>& info) { + auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() < 1) { + return Nan::ThrowTypeError("One argument required"); + } + + if (!info[0]->IsObject() || !info[1]->IsObject()) { + return Nan::ThrowTypeError("Argument must be object"); + } + + std::string sourceID, featureID; + mbgl::optional<std::string> sourceLayerID; + auto feature = Nan::To<v8::Object>(info[0]).ToLocalChecked(); + if (Nan::Has(feature, Nan::New("source").ToLocalChecked()).FromJust()) { + auto sourceOption = Nan::Get(feature, Nan::New("source").ToLocalChecked()).ToLocalChecked(); + if (!sourceOption->IsString()) { + return Nan::ThrowTypeError("Requires feature.source property to be a string"); + } + sourceID = *Nan::Utf8String(sourceOption); + } else { + return Nan::ThrowTypeError("GetFeatureState: Requires feature.source property"); + } + + if (Nan::Has(feature, Nan::New("sourceLayer").ToLocalChecked()).FromJust()) { + auto sourceLayerOption = Nan::Get(feature, Nan::New("sourceLayer").ToLocalChecked()).ToLocalChecked(); + if (!sourceLayerOption->IsString()) { + return Nan::ThrowTypeError("GetFeatureState: Requires feature.sourceLayer property to be a string"); + } + sourceLayerID = {*Nan::Utf8String(sourceLayerOption)}; + } + + if (Nan::Has(feature, Nan::New("id").ToLocalChecked()).FromJust()) { + auto idOption = Nan::Get(feature, Nan::New("id").ToLocalChecked()).ToLocalChecked(); + if (!idOption->IsString() && !(idOption->IsNumber() || idOption->IsString())) { + return Nan::ThrowTypeError("Requires feature.id property to be a string or a number"); + } + featureID = *Nan::Utf8String(idOption); + } else { + return Nan::ThrowTypeError("GetFeatureState: Requires feature.id property"); + } + + mbgl::FeatureState state; + try { + nodeMap->frontend->getRenderer()->getFeatureState(state, sourceID, sourceLayerID, featureID); + } catch (const std::exception& ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().SetUndefined(); +} + +void NodeMap::RemoveFeatureState(const Nan::FunctionCallbackInfo<v8::Value>& info) { + auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() < 1) { + return Nan::ThrowTypeError("At least one argument required"); + } + + if (!info[0]->IsObject()) { + return Nan::ThrowTypeError("Argument 1 must be object"); + } + + if (info.Length() == 2 && !info[1]->IsString()) { + return Nan::ThrowTypeError("argument 2 must be string"); + } + + std::string sourceID; + mbgl::optional<std::string> sourceLayerID, featureID, stateKey; + auto feature = Nan::To<v8::Object>(info[0]).ToLocalChecked(); + if (Nan::Has(feature, Nan::New("source").ToLocalChecked()).FromJust()) { + auto sourceOption = Nan::Get(feature, Nan::New("source").ToLocalChecked()).ToLocalChecked(); + if (!sourceOption->IsString()) { + return Nan::ThrowTypeError("Requires feature.source property to be a string"); + } + sourceID = *Nan::Utf8String(sourceOption); + } else { + return Nan::ThrowTypeError("RemoveFeatureState: Requires feature.source property"); + } + + if (Nan::Has(feature, Nan::New("sourceLayer").ToLocalChecked()).FromJust()) { + auto sourceLayerOption = Nan::Get(feature, Nan::New("sourceLayer").ToLocalChecked()).ToLocalChecked(); + if (!sourceLayerOption->IsString()) { + return Nan::ThrowTypeError("RemoveFeatureState: Requires feature.sourceLayer property to be a string"); + } + sourceLayerID = {*Nan::Utf8String(sourceLayerOption)}; + } + + if (Nan::Has(feature, Nan::New("id").ToLocalChecked()).FromJust()) { + auto idOption = Nan::Get(feature, Nan::New("id").ToLocalChecked()).ToLocalChecked(); + if (!idOption->IsString() && !(idOption->IsNumber() || idOption->IsString())) { + return Nan::ThrowTypeError("Requires feature.id property to be a string or a number"); + } + featureID = {*Nan::Utf8String(idOption)}; + } + + if (info.Length() == 2) { + auto keyParam = Nan::To<v8::String>(info[1]).ToLocalChecked(); + if (!keyParam->IsString()) { + return Nan::ThrowTypeError("RemoveFeatureState: Requires feature key property to be a string"); + } + stateKey = {*Nan::Utf8String(keyParam)}; + } + + try { + nodeMap->frontend->getRenderer()->removeFeatureState(sourceID, sourceLayerID, featureID, stateKey); + } catch (const std::exception& ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().SetUndefined(); +} + void NodeMap::DumpDebugLogs(const Nan::FunctionCallbackInfo<v8::Value>& info) { auto nodeMap = Nan::ObjectWrap::Unwrap<NodeMap>(info.Holder()); if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); diff --git a/platform/node/src/node_map.hpp b/platform/node/src/node_map.hpp index 486754c1c3..381e10381b 100644 --- a/platform/node/src/node_map.hpp +++ b/platform/node/src/node_map.hpp @@ -64,6 +64,10 @@ public: static void DumpDebugLogs(const Nan::FunctionCallbackInfo<v8::Value>&); static void QueryRenderedFeatures(const Nan::FunctionCallbackInfo<v8::Value>&); + static void SetFeatureState(const Nan::FunctionCallbackInfo<v8::Value>&); + static void GetFeatureState(const Nan::FunctionCallbackInfo<v8::Value>&); + static void RemoveFeatureState(const Nan::FunctionCallbackInfo<v8::Value>&); + static v8::Local<v8::Value> ParseError(const char* msg); void startRender(RenderOptions options); diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index d32fb6b0c4..f053b2add1 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -20,15 +20,8 @@ "query-tests/geometry/multipolygon": "needs investigation", "query-tests/geometry/polygon": "needs investigation", "query-tests/world-wrapping/box": "skip - needs issue", - "query-tests/circle-radius/feature-state": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", - "query-tests/circle-stroke-width/feature-state": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", "query-tests/fill-extrusion-translate/multiple-layers": "https://github.com/mapbox/mapbox-gl-native/issues/12701", "query-tests/fill-translate/multiple-layers": "https://github.com/mapbox/mapbox-gl-native/issues/12701", - "query-tests/line-gap-width/feature-state": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", - "query-tests/line-offset/feature-state": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", - "query-tests/line-offset/pattern-feature-state": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", - "query-tests/line-width/feature-state": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", - "query-tests/feature-state/default": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", "query-tests/regressions/mapbox-gl-js#6555": "skip - no querySourceFeatures in mbgl-node; needs issue", "render-tests/background-color/transition": "https://github.com/mapbox/mapbox-gl-native/issues/10619", "render-tests/canvas/default": "skip - js specific", @@ -52,7 +45,6 @@ "render-tests/fill-extrusion-pattern/opacity": "https://github.com/mapbox/mapbox-gl-js/issues/3327", "render-tests/fill-extrusion-pattern/feature-expression": "https://github.com/mapbox/mapbox-gl-js/issues/3327", "render-tests/fill-extrusion-pattern/tile-buffer": "https://github.com/mapbox/mapbox-gl-js/issues/3327", - "render-tests/fill-pattern/update-feature-state": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue", "render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary", "render-tests/mixed-zoom/z10-z11": "https://github.com/mapbox/mapbox-gl-native/issues/10397", "render-tests/raster-masking/overlapping-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/10195", @@ -73,19 +65,12 @@ "render-tests/symbol-cross-fade/chinese": "https://github.com/mapbox/mapbox-gl-native/issues/10619", "render-tests/video/default": "skip - https://github.com/mapbox/mapbox-gl-native/issues/601", "render-tests/background-color/colorSpace-hcl": "needs issue", - "render-tests/feature-state/composite-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12613", - "render-tests/feature-state/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12613", - "render-tests/feature-state/vector-source": "https://github.com/mapbox/mapbox-gl-native/issues/12613", "render-tests/text-variable-anchor/remember-last-placement": "skip - fails on gl-native, as symbol index is not functional at static map mode - needs issue", - "render-tests/remove-feature-state/composite-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12413", - "render-tests/remove-feature-state/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/12413", - "render-tests/remove-feature-state/vector-source": "https://github.com/mapbox/mapbox-gl-native/issues/12413", "render-tests/regressions/mapbox-gl-js#8026": "skip - js specific", "render-tests/fill-extrusion-geometry/linestring": "https://github.com/mapbox/mapbox-gl-native/pull/14240", "render-tests/circle-sort-key/literal": "https://github.com/mapbox/mapbox-gl-native/issues/15008", "render-tests/fill-sort-key/literal": "https://github.com/mapbox/mapbox-gl-native/issues/15008", "render-tests/line-sort-key/literal": "https://github.com/mapbox/mapbox-gl-native/issues/15008", - "query-tests/remove-feature-state/default": "https://github.com/mapbox/mapbox-gl-native/issues/12413", "query-tests/fill-extrusion/base-in": "https://github.com/mapbox/mapbox-gl-native/issues/13139", "query-tests/fill-extrusion/box-in": "https://github.com/mapbox/mapbox-gl-native/issues/13139", "query-tests/fill-extrusion/side-in": "https://github.com/mapbox/mapbox-gl-native/issues/13139", diff --git a/platform/node/test/js/map.test.js b/platform/node/test/js/map.test.js index b21c1519e3..d085e32f4c 100644 --- a/platform/node/test/js/map.test.js +++ b/platform/node/test/js/map.test.js @@ -126,6 +126,9 @@ test('Map', function(t) { 'setAxonometric', 'setXSkew', 'setYSkew', + 'setFeatureState', + 'getFeatureState', + 'removeFeatureState', 'dumpDebugLogs', 'queryRenderedFeatures' ]); diff --git a/platform/node/test/suite_implementation.js b/platform/node/test/suite_implementation.js index f868af8ece..386bd41092 100644 --- a/platform/node/test/suite_implementation.js +++ b/platform/node/test/suite_implementation.js @@ -122,6 +122,12 @@ export default function (style, options, callback) { map.load(operation[1]); applyOperations(operations.slice(1), callback); + } else if (operation[0] ==='setFeatureState' || operation[0] ==='getFeatureState' || operation[0] ==='removeFeatureState') { + map.render(options, function () { + map[operation[0]].apply(map, operation.slice(1)); + applyOperations(operations.slice(1), callback); + }); + } else { // Ensure that the next `map.render(options)` does not overwrite this change. if (operation[0] === 'setCenter') { |