From 3f5ea169f5d617455b21467fbd12a812c6aa2972 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 10 Jun 2016 21:17:58 -0700 Subject: [PATCH] [node] Partial implementation of runtime styling API for node bindings --- package.json | 2 +- platform/node/src/node_map.cpp | 76 +++++++++++ platform/node/src/node_map.hpp | 3 + platform/node/src/node_style.hpp | 126 ++++++++++++++++++ platform/node/src/node_style_properties.hpp | 120 +++++++++++++++++ .../node/src/node_style_properties.hpp.ejs | 35 +++++ platform/node/test/suite_implementation.js | 39 ++++-- scripts/generate-style-code.js | 17 ++- 8 files changed, 403 insertions(+), 15 deletions(-) create mode 100644 platform/node/src/node_style.hpp create mode 100644 platform/node/src/node_style_properties.hpp create mode 100644 platform/node/src/node_style_properties.hpp.ejs diff --git a/package.json b/package.json index 8bebceeb54c..ff341ffc7cd 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "express": "^4.11.1", "mapbox-gl-shaders": "mapbox/mapbox-gl-shaders#09ee512cd59a8fb1a241c78833b7c8022bf4f263", "mapbox-gl-style-spec": "mapbox/mapbox-gl-style-spec#2461efc3d883f2f2e56a6c6b2bfd7d54bbfe9f86", - "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#e78f09dce98080a9a6fa436d11fdf6fc2f271d7a", + "mapbox-gl-test-suite": "mapbox/mapbox-gl-test-suite#ce8146c048487dab444d92ce06ef0b0ca8515c73", "node-gyp": "^3.3.1", "request": "^2.72.0", "tape": "^4.5.1" diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index e423a274c42..e629bb21498 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -1,9 +1,12 @@ #include "node_map.hpp" #include "node_request.hpp" #include "node_feature.hpp" +#include "node_style_properties.hpp" #include #include +#include +#include #include @@ -48,8 +51,13 @@ NAN_MODULE_INIT(NodeMap::Init) { tpl->SetClassName(Nan::New("Map").ToLocalChecked()); Nan::SetPrototypeMethod(tpl, "load", Load); + Nan::SetPrototypeMethod(tpl, "loaded", Loaded); Nan::SetPrototypeMethod(tpl, "render", Render); Nan::SetPrototypeMethod(tpl, "release", Release); + + Nan::SetPrototypeMethod(tpl, "addClass", AddClass); + Nan::SetPrototypeMethod(tpl, "setPaintProperty", SetPaintProperty); + Nan::SetPrototypeMethod(tpl, "dumpDebugLogs", DumpDebugLogs); Nan::SetPrototypeMethod(tpl, "queryRenderedFeatures", QueryRenderedFeatures); @@ -197,6 +205,21 @@ NAN_METHOD(NodeMap::Load) { info.GetReturnValue().SetUndefined(); } +NAN_METHOD(NodeMap::Loaded) { + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + bool loaded = false; + + try { + loaded = nodeMap->map->isFullyLoaded(); + } catch (const std::exception &ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().Set(Nan::New(loaded)); +} + NodeMap::RenderOptions NodeMap::ParseOptions(v8::Local obj) { Nan::HandleScope scope; @@ -436,6 +459,59 @@ void NodeMap::release() { map.reset(); } +NAN_METHOD(NodeMap::AddClass) { + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() <= 0 || !info[0]->IsString()) { + return Nan::ThrowTypeError("First argument must be a string"); + } + + try { + nodeMap->map->addClass(*Nan::Utf8String(info[0])); + } catch (const std::exception &ex) { + return Nan::ThrowError(ex.what()); + } + + info.GetReturnValue().SetUndefined(); +} + +NAN_METHOD(NodeMap::SetPaintProperty) { + auto nodeMap = Nan::ObjectWrap::Unwrap(info.Holder()); + if (!nodeMap->map) return Nan::ThrowError(releasedMessage()); + + if (info.Length() < 3) { + return Nan::ThrowTypeError("Three arguments required"); + } + + if (!info[0]->IsString()) { + return Nan::ThrowTypeError("First argument must be a string"); + } + + mbgl::style::Layer* layer = nodeMap->map->getLayer(*Nan::Utf8String(info[0])); + if (!layer) { + return Nan::ThrowTypeError("layer not found"); + } + + if (!info[1]->IsString()) { + return Nan::ThrowTypeError("Second argument must be a string"); + } + + static const PropertySetters setters = makePaintPropertySetters(); + + auto it = setters.find(*Nan::Utf8String(info[1])); + if (it == setters.end()) { + return Nan::ThrowTypeError("property not found"); + } + + if (!it->second(*layer, info[2])) { + return; + } + + nodeMap->map->update(mbgl::Update::RecalculateStyle); + info.GetReturnValue().SetUndefined(); +} + NAN_METHOD(NodeMap::DumpDebugLogs) { auto nodeMap = Nan::ObjectWrap::Unwrap(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 d3f7bd9e41e..8dfb96eef87 100644 --- a/platform/node/src/node_map.hpp +++ b/platform/node/src/node_map.hpp @@ -24,8 +24,11 @@ class NodeMap : public Nan::ObjectWrap, static NAN_METHOD(New); static NAN_METHOD(Load); + static NAN_METHOD(Loaded); static NAN_METHOD(Render); static NAN_METHOD(Release); + static NAN_METHOD(AddClass); + static NAN_METHOD(SetPaintProperty); static NAN_METHOD(DumpDebugLogs); static NAN_METHOD(QueryRenderedFeatures); diff --git a/platform/node/src/node_style.hpp b/platform/node/src/node_style.hpp new file mode 100644 index 00000000000..b81d14c700f --- /dev/null +++ b/platform/node/src/node_style.hpp @@ -0,0 +1,126 @@ +#include +#include +#include +#include + +namespace node_mbgl { + +template +struct ValueConverter {}; + +template <> +struct ValueConverter { + mbgl::optional> operator()(const v8::Local& value) const { + if (!value->IsBoolean()) { + Nan::ThrowTypeError("boolean required"); + return {}; + } + + return { value->BooleanValue() }; + } +}; + +template <> +struct ValueConverter { + mbgl::optional> operator()(const v8::Local& value) const { + if (!value->IsNumber()) { + Nan::ThrowTypeError("number required"); + return {}; + } + + return { value->NumberValue() }; + } +}; + +template <> +struct ValueConverter { + mbgl::optional> operator()(const v8::Local& value) const { + if (!value->IsString()) { + Nan::ThrowTypeError("string required"); + return {}; + } + + return { std::string(*Nan::Utf8String(value)) }; + } +}; + +template +struct ValueConverter::value>> { + mbgl::optional> operator()(const v8::Local& value) const { + if (!value->IsString()) { + Nan::ThrowTypeError("string required"); + return {}; + } + + mbgl::optional result = mbgl::Enum::toEnum(*Nan::Utf8String(value)); + if (!result) { + Nan::ThrowTypeError("invalid enumeration value"); + return {}; + } + + return { *result }; + } +}; + +template <> +struct ValueConverter { + mbgl::optional> operator()(const v8::Local& value) const { + (void)value; + return {}; + } +}; + +template <> +struct ValueConverter> { + mbgl::optional>> operator()(const v8::Local& value) const { + (void)value; + return {}; + } +}; + +template <> +struct ValueConverter> { + mbgl::optional>> operator()(const v8::Local& value) const { + (void)value; + return {}; + } +}; + +template <> +struct ValueConverter> { + mbgl::optional>> operator()(const v8::Local& value) const { + (void)value; + return {}; + } +}; + +using PropertySetter = std::function&)>; +using PropertySetters = std::unordered_map; + +template +PropertySetter makePropertySetter(void (L::*setter)(mbgl::style::PropertyValue)) { + return [setter] (mbgl::style::Layer& layer, const v8::Local& value) { + L* typedLayer = layer.as(); + if (!typedLayer) { + Nan::ThrowTypeError("layer doesn't support this property"); + return false; + } + + mbgl::optional> typedValue; + + if (value->IsNull() || value->IsUndefined()) { + typedValue = mbgl::style::PropertyValue(); + } else { + typedValue = ValueConverter()(value); + } + + if (!typedValue) { + return false; + } + + (typedLayer->*setter)(*typedValue); + return true; + }; +} + +} diff --git a/platform/node/src/node_style_properties.hpp b/platform/node/src/node_style_properties.hpp new file mode 100644 index 00000000000..4702918ae19 --- /dev/null +++ b/platform/node/src/node_style_properties.hpp @@ -0,0 +1,120 @@ +#include "node_style.hpp" + +#include +#include +#include +#include +#include +#include + +namespace node_mbgl { + +inline PropertySetters makeLayoutPropertySetters() { + using namespace mbgl::style; + PropertySetters result; + + + result["line-cap"] = makePropertySetter(&LineLayer::setLineCap); + result["line-join"] = makePropertySetter(&LineLayer::setLineJoin); + result["line-miter-limit"] = makePropertySetter(&LineLayer::setLineMiterLimit); + result["line-round-limit"] = makePropertySetter(&LineLayer::setLineRoundLimit); + + result["symbol-placement"] = makePropertySetter(&SymbolLayer::setSymbolPlacement); + result["symbol-spacing"] = makePropertySetter(&SymbolLayer::setSymbolSpacing); + result["symbol-avoid-edges"] = makePropertySetter(&SymbolLayer::setSymbolAvoidEdges); + result["icon-allow-overlap"] = makePropertySetter(&SymbolLayer::setIconAllowOverlap); + result["icon-ignore-placement"] = makePropertySetter(&SymbolLayer::setIconIgnorePlacement); + result["icon-optional"] = makePropertySetter(&SymbolLayer::setIconOptional); + result["icon-rotation-alignment"] = makePropertySetter(&SymbolLayer::setIconRotationAlignment); + result["icon-size"] = makePropertySetter(&SymbolLayer::setIconSize); + result["icon-image"] = makePropertySetter(&SymbolLayer::setIconImage); + result["icon-rotate"] = makePropertySetter(&SymbolLayer::setIconRotate); + result["icon-padding"] = makePropertySetter(&SymbolLayer::setIconPadding); + result["icon-keep-upright"] = makePropertySetter(&SymbolLayer::setIconKeepUpright); + result["icon-offset"] = makePropertySetter(&SymbolLayer::setIconOffset); + result["text-rotation-alignment"] = makePropertySetter(&SymbolLayer::setTextRotationAlignment); + result["text-field"] = makePropertySetter(&SymbolLayer::setTextField); + result["text-font"] = makePropertySetter(&SymbolLayer::setTextFont); + result["text-size"] = makePropertySetter(&SymbolLayer::setTextSize); + result["text-max-width"] = makePropertySetter(&SymbolLayer::setTextMaxWidth); + result["text-line-height"] = makePropertySetter(&SymbolLayer::setTextLineHeight); + result["text-letter-spacing"] = makePropertySetter(&SymbolLayer::setTextLetterSpacing); + result["text-justify"] = makePropertySetter(&SymbolLayer::setTextJustify); + result["text-anchor"] = makePropertySetter(&SymbolLayer::setTextAnchor); + result["text-max-angle"] = makePropertySetter(&SymbolLayer::setTextMaxAngle); + result["text-rotate"] = makePropertySetter(&SymbolLayer::setTextRotate); + result["text-padding"] = makePropertySetter(&SymbolLayer::setTextPadding); + result["text-keep-upright"] = makePropertySetter(&SymbolLayer::setTextKeepUpright); + result["text-transform"] = makePropertySetter(&SymbolLayer::setTextTransform); + result["text-offset"] = makePropertySetter(&SymbolLayer::setTextOffset); + result["text-allow-overlap"] = makePropertySetter(&SymbolLayer::setTextAllowOverlap); + result["text-ignore-placement"] = makePropertySetter(&SymbolLayer::setTextIgnorePlacement); + result["text-optional"] = makePropertySetter(&SymbolLayer::setTextOptional); + + + + + return result; +} + +inline PropertySetters makePaintPropertySetters() { + using namespace mbgl::style; + PropertySetters result; + + result["fill-antialias"] = makePropertySetter(&FillLayer::setFillAntialias); + result["fill-opacity"] = makePropertySetter(&FillLayer::setFillOpacity); + result["fill-color"] = makePropertySetter(&FillLayer::setFillColor); + result["fill-outline-color"] = makePropertySetter(&FillLayer::setFillOutlineColor); + result["fill-translate"] = makePropertySetter(&FillLayer::setFillTranslate); + result["fill-translate-anchor"] = makePropertySetter(&FillLayer::setFillTranslateAnchor); + result["fill-pattern"] = makePropertySetter(&FillLayer::setFillPattern); + + result["line-opacity"] = makePropertySetter(&LineLayer::setLineOpacity); + result["line-color"] = makePropertySetter(&LineLayer::setLineColor); + result["line-translate"] = makePropertySetter(&LineLayer::setLineTranslate); + result["line-translate-anchor"] = makePropertySetter(&LineLayer::setLineTranslateAnchor); + result["line-width"] = makePropertySetter(&LineLayer::setLineWidth); + result["line-gap-width"] = makePropertySetter(&LineLayer::setLineGapWidth); + result["line-offset"] = makePropertySetter(&LineLayer::setLineOffset); + result["line-blur"] = makePropertySetter(&LineLayer::setLineBlur); + result["line-dasharray"] = makePropertySetter(&LineLayer::setLineDasharray); + result["line-pattern"] = makePropertySetter(&LineLayer::setLinePattern); + + result["icon-opacity"] = makePropertySetter(&SymbolLayer::setIconOpacity); + result["icon-color"] = makePropertySetter(&SymbolLayer::setIconColor); + result["icon-halo-color"] = makePropertySetter(&SymbolLayer::setIconHaloColor); + result["icon-halo-width"] = makePropertySetter(&SymbolLayer::setIconHaloWidth); + result["icon-halo-blur"] = makePropertySetter(&SymbolLayer::setIconHaloBlur); + result["icon-translate"] = makePropertySetter(&SymbolLayer::setIconTranslate); + result["icon-translate-anchor"] = makePropertySetter(&SymbolLayer::setIconTranslateAnchor); + result["text-opacity"] = makePropertySetter(&SymbolLayer::setTextOpacity); + result["text-color"] = makePropertySetter(&SymbolLayer::setTextColor); + result["text-halo-color"] = makePropertySetter(&SymbolLayer::setTextHaloColor); + result["text-halo-width"] = makePropertySetter(&SymbolLayer::setTextHaloWidth); + result["text-halo-blur"] = makePropertySetter(&SymbolLayer::setTextHaloBlur); + result["text-translate"] = makePropertySetter(&SymbolLayer::setTextTranslate); + result["text-translate-anchor"] = makePropertySetter(&SymbolLayer::setTextTranslateAnchor); + + result["circle-radius"] = makePropertySetter(&CircleLayer::setCircleRadius); + result["circle-color"] = makePropertySetter(&CircleLayer::setCircleColor); + result["circle-blur"] = makePropertySetter(&CircleLayer::setCircleBlur); + result["circle-opacity"] = makePropertySetter(&CircleLayer::setCircleOpacity); + result["circle-translate"] = makePropertySetter(&CircleLayer::setCircleTranslate); + result["circle-translate-anchor"] = makePropertySetter(&CircleLayer::setCircleTranslateAnchor); + + result["raster-opacity"] = makePropertySetter(&RasterLayer::setRasterOpacity); + result["raster-hue-rotate"] = makePropertySetter(&RasterLayer::setRasterHueRotate); + result["raster-brightness-min"] = makePropertySetter(&RasterLayer::setRasterBrightnessMin); + result["raster-brightness-max"] = makePropertySetter(&RasterLayer::setRasterBrightnessMax); + result["raster-saturation"] = makePropertySetter(&RasterLayer::setRasterSaturation); + result["raster-contrast"] = makePropertySetter(&RasterLayer::setRasterContrast); + result["raster-fade-duration"] = makePropertySetter(&RasterLayer::setRasterFadeDuration); + + result["background-color"] = makePropertySetter(&BackgroundLayer::setBackgroundColor); + result["background-pattern"] = makePropertySetter(&BackgroundLayer::setBackgroundPattern); + result["background-opacity"] = makePropertySetter(&BackgroundLayer::setBackgroundOpacity); + + return result; +} + +} diff --git a/platform/node/src/node_style_properties.hpp.ejs b/platform/node/src/node_style_properties.hpp.ejs new file mode 100644 index 00000000000..1937421fa50 --- /dev/null +++ b/platform/node/src/node_style_properties.hpp.ejs @@ -0,0 +1,35 @@ +#include "node_style.hpp" + +<% for (const layer of locals.layers) { -%> +#include _layer.hpp> +<% } -%> + +namespace node_mbgl { + +inline PropertySetters makeLayoutPropertySetters() { + using namespace mbgl::style; + PropertySetters result; + +<% for (const layer of locals.layers) { -%> +<% for (const property of layer.layoutProperties) { -%> + result["<%- property.name %>"] = makePropertySetter(&<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>); +<% } -%> + +<% } -%> + return result; +} + +inline PropertySetters makePaintPropertySetters() { + using namespace mbgl::style; + PropertySetters result; + +<% for (const layer of locals.layers) { -%> +<% for (const property of layer.paintProperties) { -%> + result["<%- property.name %>"] = makePropertySetter(&<%- camelize(layer.type) %>Layer::set<%- camelize(property.name) %>); +<% } -%> + +<% } -%> + return result; +} + +} diff --git a/platform/node/test/suite_implementation.js b/platform/node/test/suite_implementation.js index da226a68f48..4369c81ffce 100644 --- a/platform/node/test/suite_implementation.js +++ b/platform/node/test/suite_implementation.js @@ -41,16 +41,39 @@ module.exports = function (style, options, callback) { map.load(style); - map.render(options, function (err, pixels) { - var results = options.queryGeometry ? - map.queryRenderedFeatures(options.queryGeometry) : - []; - map.release(); - if (timedOut) return; - clearTimeout(watchdog); - callback(err, pixels, results.map(prepareFeatures)); + applyOperations(options.operations, function() { + map.render(options, function (err, pixels) { + var results = options.queryGeometry ? + map.queryRenderedFeatures(options.queryGeometry) : + []; + map.release(); + if (timedOut) return; + clearTimeout(watchdog); + callback(err, pixels, results.map(prepareFeatures)); + }); }); + function applyOperations(operations, callback) { + var operation = operations && operations[0]; + if (!operations || operations.length === 0) { + callback(); + + } else if (operation[0] === 'wait') { + var wait = function() { + if (map.loaded()) { + applyOperations(operations.slice(1), callback); + } else { + map.render(options, wait); + } + }; + wait(); + + } else { + map[operation[0]].apply(map, operation.slice(1)); + applyOperations(operations.slice(1), callback); + } + } + function prepareFeatures(r) { delete r.layer; return r; diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index 21654117b8f..6469cf55fd1 100644 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -81,7 +81,7 @@ const layerCpp = ejs.compile(fs.readFileSync('src/mbgl/style/layers/layer.cpp.ej const propertiesHpp = ejs.compile(fs.readFileSync('src/mbgl/style/layers/layer_properties.hpp.ejs', 'utf8'), {strict: true}); const propertiesCpp = ejs.compile(fs.readFileSync('src/mbgl/style/layers/layer_properties.cpp.ejs', 'utf8'), {strict: true}); -for (const type of spec.layer.type.values) { +const layers = spec.layer.type.values.map((type) => { const layoutProperties = Object.keys(spec[`layout_${type}`]).reduce((memo, name) => { if (name !== 'visibility') { spec[`layout_${type}`][name].name = name; @@ -96,15 +96,20 @@ for (const type of spec.layer.type.values) { return memo; }, []); - const layer = { + return { type: type, layoutProperties: layoutProperties, paintProperties: paintProperties, }; +}); - fs.writeFileSync(`include/mbgl/style/layers/${type}_layer.hpp`, layerHpp(layer)); - fs.writeFileSync(`src/mbgl/style/layers/${type}_layer.cpp`, layerCpp(layer)); +for (const layer of layers) { + fs.writeFileSync(`include/mbgl/style/layers/${layer.type}_layer.hpp`, layerHpp(layer)); + fs.writeFileSync(`src/mbgl/style/layers/${layer.type}_layer.cpp`, layerCpp(layer)); - fs.writeFileSync(`src/mbgl/style/layers/${type}_layer_properties.hpp`, propertiesHpp(layer)); - fs.writeFileSync(`src/mbgl/style/layers/${type}_layer_properties.cpp`, propertiesCpp(layer)); + fs.writeFileSync(`src/mbgl/style/layers/${layer.type}_layer_properties.hpp`, propertiesHpp(layer)); + fs.writeFileSync(`src/mbgl/style/layers/${layer.type}_layer_properties.cpp`, propertiesCpp(layer)); } + +const nodeStyleCpp = ejs.compile(fs.readFileSync('platform/node/src/node_style_properties.hpp.ejs', 'utf8'), {strict: true}); +fs.writeFileSync('platform/node/src/node_style_properties.hpp', nodeStyleCpp({layers: layers}));