From 82de856c94bbc090ba30186011610da5e233e277 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Thu, 15 Feb 2018 17:38:23 +0200 Subject: [core, ios, macos, android, node] Heatmap layer (#11046) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Konstantin Käfer Co-Authored-By: Anand Thakker Co-Authored-By: Minh Nguyễn <1ec5@users.noreply.github.com> --- cmake/core-files.cmake | 20 + .../conversion/heatmap_color_property_value.hpp | 46 ++ .../mbgl/style/heatmap_color_property_value.hpp | 49 ++ include/mbgl/style/layer.hpp | 3 + include/mbgl/style/layer_type.hpp | 1 + include/mbgl/style/layers/heatmap_layer.hpp | 86 +++ include/mbgl/style/layers/layer.hpp.ejs | 3 + mapbox-gl-js | 2 +- platform/android/CHANGELOG.md | 4 + .../mapboxsdk/style/layers/HeatmapLayer.java | 221 ++++++ .../mapboxsdk/style/layers/PropertyFactory.java | 132 ++++ .../mapboxsdk/testapp/style/HeatmapLayerTest.java | 626 ++++++++++++++++ .../src/main/AndroidManifest.xml | 11 + .../activity/style/HeatmapLayerActivity.java | 216 ++++++ .../src/main/res/layout/activity_heatmaplayer.xml | 14 + .../src/main/res/values/descriptions.xml | 1 + .../src/main/res/values/titles.xml | 1 + platform/android/config.cmake | 2 + platform/android/scripts/generate-style-code.js | 3 + .../android/src/style/layers/heatmap_layer.cpp | 134 ++++ .../android/src/style/layers/heatmap_layer.hpp | 50 ++ platform/android/src/style/layers/layer.cpp | 1 + platform/android/src/style/layers/layers.cpp | 4 + platform/darwin/scripts/generate-style-code.js | 11 +- .../darwin/scripts/style-spec-overrides-v8.json | 10 +- platform/darwin/src/MGLHeatmapStyleLayer.h | 189 +++++ platform/darwin/src/MGLHeatmapStyleLayer.mm | 210 ++++++ platform/darwin/src/MGLStyle.mm | 4 + platform/darwin/src/MGLStyleLayer.mm.ejs | 4 +- platform/darwin/src/MGLStyleValue_Private.h | 36 +- platform/darwin/src/MGLVectorStyleLayer.h | 8 +- .../darwin/test/MGLDocumentationExampleTests.swift | 18 + platform/darwin/test/MGLHeatmapColorTests.mm | 61 ++ platform/darwin/test/MGLHeatmapStyleLayerTests.mm | 296 ++++++++ platform/darwin/test/MGLStyleLayerTests.mm.ejs | 2 + platform/ios/CHANGELOG.md | 1 + platform/ios/docs/guides/For Style Authors.md | 1 + platform/ios/ios.xcodeproj/project.pbxproj | 20 + platform/ios/jazzy.yml | 1 + platform/ios/src/Mapbox.h | 1 + platform/macos/CHANGELOG.md | 1 + .../Layers/heatmap.imageset/Contents.json | 16 + .../Layers/heatmap.imageset/heatmap.pdf | Bin 0 -> 1262 bytes platform/macos/app/StyleLayerIconTransformer.m | 3 + platform/macos/app/heatmap.json | 809 +++++++++++++++++++++ platform/macos/app/resources/heatmap.svg | 72 ++ platform/macos/docs/guides/For Style Authors.md | 1 + platform/macos/jazzy.yml | 1 + platform/macos/macos.xcodeproj/project.pbxproj | 20 + platform/macos/src/Mapbox.h | 1 + platform/node/src/node_map.cpp | 1 + platform/node/test/ignores.json | 18 - scripts/generate-shaders.js | 3 - scripts/generate-style-code.js | 2 + scripts/style-spec.js | 2 - src/mbgl/gl/color_mode.hpp | 4 + src/mbgl/gl/context.cpp | 31 +- src/mbgl/gl/context.hpp | 24 +- src/mbgl/gl/types.hpp | 9 + src/mbgl/programs/attributes.hpp | 7 +- src/mbgl/programs/heatmap_program.cpp | 7 + src/mbgl/programs/heatmap_program.hpp | 49 ++ src/mbgl/programs/heatmap_texture_program.cpp | 7 + src/mbgl/programs/heatmap_texture_program.hpp | 43 ++ src/mbgl/programs/programs.hpp | 6 + src/mbgl/programs/uniforms.hpp | 5 + src/mbgl/renderer/buckets/heatmap_bucket.cpp | 98 +++ src/mbgl/renderer/buckets/heatmap_bucket.hpp | 40 + src/mbgl/renderer/layers/render_heatmap_layer.cpp | 178 +++++ src/mbgl/renderer/layers/render_heatmap_layer.hpp | 48 ++ src/mbgl/renderer/render_layer.cpp | 3 + src/mbgl/renderer/renderer_impl.cpp | 11 +- src/mbgl/shaders/heatmap.cpp | 128 ++++ src/mbgl/shaders/heatmap.hpp | 16 + src/mbgl/shaders/heatmap_texture.cpp | 42 ++ src/mbgl/shaders/heatmap_texture.hpp | 16 + src/mbgl/style/conversion/layer.cpp | 3 + .../style/conversion/make_property_setters.hpp | 13 + src/mbgl/style/conversion/property_setter.hpp | 1 + src/mbgl/style/layers/heatmap_layer.cpp | 239 ++++++ src/mbgl/style/layers/heatmap_layer_impl.cpp | 15 + src/mbgl/style/layers/heatmap_layer_impl.hpp | 21 + src/mbgl/style/layers/heatmap_layer_properties.cpp | 9 + src/mbgl/style/layers/heatmap_layer_properties.hpp | 40 + src/mbgl/style/layers/layer.cpp.ejs | 12 + src/mbgl/style/layers/layer_properties.hpp.ejs | 1 + src/mbgl/style/paint_property.hpp | 23 + src/mbgl/style/style_impl.cpp | 1 + src/mbgl/util/offscreen_texture.cpp | 22 +- src/mbgl/util/offscreen_texture.hpp | 6 +- 90 files changed, 4570 insertions(+), 61 deletions(-) create mode 100644 include/mbgl/style/conversion/heatmap_color_property_value.hpp create mode 100644 include/mbgl/style/heatmap_color_property_value.hpp create mode 100644 include/mbgl/style/layers/heatmap_layer.hpp create mode 100644 platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java create mode 100644 platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml create mode 100644 platform/android/src/style/layers/heatmap_layer.cpp create mode 100644 platform/android/src/style/layers/heatmap_layer.hpp create mode 100644 platform/darwin/src/MGLHeatmapStyleLayer.h create mode 100644 platform/darwin/src/MGLHeatmapStyleLayer.mm create mode 100644 platform/darwin/test/MGLHeatmapColorTests.mm create mode 100644 platform/darwin/test/MGLHeatmapStyleLayerTests.mm create mode 100644 platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json create mode 100644 platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf create mode 100644 platform/macos/app/heatmap.json create mode 100644 platform/macos/app/resources/heatmap.svg create mode 100644 src/mbgl/programs/heatmap_program.cpp create mode 100644 src/mbgl/programs/heatmap_program.hpp create mode 100644 src/mbgl/programs/heatmap_texture_program.cpp create mode 100644 src/mbgl/programs/heatmap_texture_program.hpp create mode 100644 src/mbgl/renderer/buckets/heatmap_bucket.cpp create mode 100644 src/mbgl/renderer/buckets/heatmap_bucket.hpp create mode 100644 src/mbgl/renderer/layers/render_heatmap_layer.cpp create mode 100644 src/mbgl/renderer/layers/render_heatmap_layer.hpp create mode 100644 src/mbgl/shaders/heatmap.cpp create mode 100644 src/mbgl/shaders/heatmap.hpp create mode 100644 src/mbgl/shaders/heatmap_texture.cpp create mode 100644 src/mbgl/shaders/heatmap_texture.hpp create mode 100644 src/mbgl/style/layers/heatmap_layer.cpp create mode 100644 src/mbgl/style/layers/heatmap_layer_impl.cpp create mode 100644 src/mbgl/style/layers/heatmap_layer_impl.hpp create mode 100644 src/mbgl/style/layers/heatmap_layer_properties.cpp create mode 100644 src/mbgl/style/layers/heatmap_layer_properties.hpp diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index a0f75a6a59..f24482e301 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -146,6 +146,10 @@ set(MBGL_CORE_FILES src/mbgl/programs/fill_extrusion_program.hpp src/mbgl/programs/fill_program.cpp src/mbgl/programs/fill_program.hpp + src/mbgl/programs/heatmap_program.cpp + src/mbgl/programs/heatmap_program.hpp + src/mbgl/programs/heatmap_texture_program.cpp + src/mbgl/programs/heatmap_texture_program.hpp src/mbgl/programs/hillshade_prepare_program.cpp src/mbgl/programs/hillshade_prepare_program.hpp src/mbgl/programs/hillshade_program.cpp @@ -225,6 +229,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/buckets/fill_bucket.hpp src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp + src/mbgl/renderer/buckets/heatmap_bucket.cpp + src/mbgl/renderer/buckets/heatmap_bucket.hpp src/mbgl/renderer/buckets/hillshade_bucket.cpp src/mbgl/renderer/buckets/hillshade_bucket.hpp src/mbgl/renderer/buckets/line_bucket.cpp @@ -245,6 +251,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp src/mbgl/renderer/layers/render_fill_layer.cpp src/mbgl/renderer/layers/render_fill_layer.hpp + src/mbgl/renderer/layers/render_heatmap_layer.cpp + src/mbgl/renderer/layers/render_heatmap_layer.hpp src/mbgl/renderer/layers/render_hillshade_layer.cpp src/mbgl/renderer/layers/render_hillshade_layer.hpp src/mbgl/renderer/layers/render_line_layer.cpp @@ -297,6 +305,10 @@ set(MBGL_CORE_FILES src/mbgl/shaders/fill_outline_pattern.hpp src/mbgl/shaders/fill_pattern.cpp src/mbgl/shaders/fill_pattern.hpp + src/mbgl/shaders/heatmap.cpp + src/mbgl/shaders/heatmap.hpp + src/mbgl/shaders/heatmap_texture.cpp + src/mbgl/shaders/heatmap_texture.hpp src/mbgl/shaders/hillshade.cpp src/mbgl/shaders/hillshade.hpp src/mbgl/shaders/hillshade_prepare.cpp @@ -349,6 +361,7 @@ set(MBGL_CORE_FILES include/mbgl/style/data_driven_property_value.hpp include/mbgl/style/filter.hpp include/mbgl/style/filter_evaluator.hpp + include/mbgl/style/heatmap_color_property_value.hpp include/mbgl/style/image.hpp include/mbgl/style/layer.hpp include/mbgl/style/layer_type.hpp @@ -401,6 +414,7 @@ set(MBGL_CORE_FILES include/mbgl/style/conversion/geojson.hpp include/mbgl/style/conversion/geojson_options.hpp include/mbgl/style/conversion/get_json_type.hpp + include/mbgl/style/conversion/heatmap_color_property_value.hpp include/mbgl/style/conversion/layer.hpp include/mbgl/style/conversion/light.hpp include/mbgl/style/conversion/position.hpp @@ -495,6 +509,7 @@ set(MBGL_CORE_FILES include/mbgl/style/layers/custom_layer.hpp include/mbgl/style/layers/fill_extrusion_layer.hpp include/mbgl/style/layers/fill_layer.hpp + include/mbgl/style/layers/heatmap_layer.hpp include/mbgl/style/layers/hillshade_layer.hpp include/mbgl/style/layers/line_layer.hpp include/mbgl/style/layers/raster_layer.hpp @@ -522,6 +537,11 @@ set(MBGL_CORE_FILES src/mbgl/style/layers/fill_layer_impl.hpp src/mbgl/style/layers/fill_layer_properties.cpp src/mbgl/style/layers/fill_layer_properties.hpp + src/mbgl/style/layers/heatmap_layer.cpp + src/mbgl/style/layers/heatmap_layer_impl.cpp + src/mbgl/style/layers/heatmap_layer_impl.hpp + src/mbgl/style/layers/heatmap_layer_properties.cpp + src/mbgl/style/layers/heatmap_layer_properties.hpp src/mbgl/style/layers/hillshade_layer.cpp src/mbgl/style/layers/hillshade_layer_impl.cpp src/mbgl/style/layers/hillshade_layer_impl.hpp diff --git a/include/mbgl/style/conversion/heatmap_color_property_value.hpp b/include/mbgl/style/conversion/heatmap_color_property_value.hpp new file mode 100644 index 0000000000..e3689c524c --- /dev/null +++ b/include/mbgl/style/conversion/heatmap_color_property_value.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter { + optional operator()(const Convertible& value, Error& error) const { + if (isUndefined(value)) { + return HeatmapColorPropertyValue(); + } else if (isExpression(value)) { + optional> expression = convert>(value, error, expression::type::Color); + if (!expression) { + return {}; + } + assert(*expression); + if (!isFeatureConstant(**expression)) { + error = { "property expressions not supported" }; + return {}; + } + if (!isZoomConstant(**expression)) { + error = { "zoom expressions not supported" }; + return {}; + } + return {HeatmapColorPropertyValue(std::move(*expression))}; + } else { + error = { "heatmap-color must be an expression" }; + return {}; + } + } +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/heatmap_color_property_value.hpp b/include/mbgl/style/heatmap_color_property_value.hpp new file mode 100644 index 0000000000..130639c6e2 --- /dev/null +++ b/include/mbgl/style/heatmap_color_property_value.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace style { + +/* + * Special-case implementation of (a subset of) the PropertyValue interface + * used for building the HeatmapColor paint property traits class. + */ +class HeatmapColorPropertyValue { +private: + std::shared_ptr value; + + friend bool operator==(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return (lhs.isUndefined() && rhs.isUndefined()) || (lhs.value && rhs.value && *(lhs.value) == *(rhs.value)); + } + + friend bool operator!=(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return !(lhs == rhs); + } + +public: + HeatmapColorPropertyValue() : value(nullptr) {} + HeatmapColorPropertyValue(std::shared_ptr value_) : value(std::move(value_)) {} + + bool isUndefined() const { return value.get() == nullptr; } + + // noop, needed for batch evaluation of paint property values to compile + template + Color evaluate(const Evaluator&, TimePoint = {}) const { return {}; } + + Color evaluate(double heatmapDensity) const { + const auto result = value->evaluate(expression::EvaluationContext({}, nullptr, {heatmapDensity})); + return *expression::fromExpressionValue(*result); + } + + bool isDataDriven() const { return false; } + bool hasDataDrivenPropertyDifference(const HeatmapColorPropertyValue&) const { return false; } + + const expression::Expression& getExpression() const { return *value; } +}; + + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp index 8a5a4236b3..12494f5387 100644 --- a/include/mbgl/style/layer.hpp +++ b/include/mbgl/style/layer.hpp @@ -23,6 +23,7 @@ class HillshadeLayer; class BackgroundLayer; class CustomLayer; class FillExtrusionLayer; +class HeatmapLayer; class LayerObserver; /** @@ -93,6 +94,8 @@ public: return std::forward(visitor)(*as()); case LayerType::FillExtrusion: return std::forward(visitor)(*as()); + case LayerType::Heatmap: + return std::forward(visitor)(*as()); } diff --git a/include/mbgl/style/layer_type.hpp b/include/mbgl/style/layer_type.hpp index 951757134b..0987ea4d0a 100644 --- a/include/mbgl/style/layer_type.hpp +++ b/include/mbgl/style/layer_type.hpp @@ -13,6 +13,7 @@ enum class LayerType { Background, Custom, FillExtrusion, + Heatmap, }; } // namespace style diff --git a/include/mbgl/style/layers/heatmap_layer.hpp b/include/mbgl/style/layers/heatmap_layer.hpp new file mode 100644 index 0000000000..33d927ad38 --- /dev/null +++ b/include/mbgl/style/layers/heatmap_layer.hpp @@ -0,0 +1,86 @@ +// This file is generated. Do not edit. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace style { + +class TransitionOptions; + +class HeatmapLayer : public Layer { +public: + HeatmapLayer(const std::string& layerID, const std::string& sourceID); + ~HeatmapLayer() final; + + // Source + const std::string& getSourceID() const; + const std::string& getSourceLayer() const; + void setSourceLayer(const std::string& sourceLayer); + + void setFilter(const Filter&); + const Filter& getFilter() const; + + // Visibility + void setVisibility(VisibilityType) final; + + // Zoom range + void setMinZoom(float) final; + void setMaxZoom(float) final; + + // Paint properties + + static DataDrivenPropertyValue getDefaultHeatmapRadius(); + DataDrivenPropertyValue getHeatmapRadius() const; + void setHeatmapRadius(DataDrivenPropertyValue); + void setHeatmapRadiusTransition(const TransitionOptions&); + TransitionOptions getHeatmapRadiusTransition() const; + + static DataDrivenPropertyValue getDefaultHeatmapWeight(); + DataDrivenPropertyValue getHeatmapWeight() const; + void setHeatmapWeight(DataDrivenPropertyValue); + void setHeatmapWeightTransition(const TransitionOptions&); + TransitionOptions getHeatmapWeightTransition() const; + + static PropertyValue getDefaultHeatmapIntensity(); + PropertyValue getHeatmapIntensity() const; + void setHeatmapIntensity(PropertyValue); + void setHeatmapIntensityTransition(const TransitionOptions&); + TransitionOptions getHeatmapIntensityTransition() const; + + static HeatmapColorPropertyValue getDefaultHeatmapColor(); + HeatmapColorPropertyValue getHeatmapColor() const; + void setHeatmapColor(HeatmapColorPropertyValue); + void setHeatmapColorTransition(const TransitionOptions&); + TransitionOptions getHeatmapColorTransition() const; + + static PropertyValue getDefaultHeatmapOpacity(); + PropertyValue getHeatmapOpacity() const; + void setHeatmapOpacity(PropertyValue); + void setHeatmapOpacityTransition(const TransitionOptions&); + TransitionOptions getHeatmapOpacityTransition() const; + + // Private implementation + + class Impl; + const Impl& impl() const; + + Mutable mutableImpl() const; + HeatmapLayer(Immutable); + std::unique_ptr cloneRef(const std::string& id) const final; +}; + +template <> +inline bool Layer::is() const { + return getType() == LayerType::Heatmap; +} + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs index 265dd57e1f..6d40405ccd 100644 --- a/include/mbgl/style/layers/layer.hpp.ejs +++ b/include/mbgl/style/layers/layer.hpp.ejs @@ -11,6 +11,9 @@ #include #include #include +<% if (type === 'heatmap') { -%> +#include +<% } -%> #include diff --git a/mapbox-gl-js b/mapbox-gl-js index f13c86ea35..d61850a6bd 160000 --- a/mapbox-gl-js +++ b/mapbox-gl-js @@ -1 +1 @@ -Subproject commit f13c86ea356c384fdab31855b9152f5bf5ef97b8 +Subproject commit d61850a6bd84ce6d697be392c7ef6d4b5555c20e diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index afac6d4902..fa9b1d29cc 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,6 +2,10 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. +## master + + - HeatmapLayer [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046) + ## 6.0.0-beta.2 - February 13, 2018 - Deprecate LocationEngine [#11185](https://github.com/mapbox/mapbox-gl-native/pull/11185) - Remove LOST from SDK [11186](https://github.com/mapbox/mapbox-gl-native/pull/11186) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java new file mode 100644 index 0000000000..6b8fd65def --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java @@ -0,0 +1,221 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +package com.mapbox.mapboxsdk.style.layers; + +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; + +import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; + +import com.mapbox.mapboxsdk.style.layers.TransitionOptions; + +/** + * A heatmap. + * + * @see The online documentation + */ +@UiThread +public class HeatmapLayer extends Layer { + + /** + * Creates a HeatmapLayer. + * + * @param nativePtr pointer used by core + */ + public HeatmapLayer(long nativePtr) { + super(nativePtr); + } + + /** + * Creates a HeatmapLayer. + * + * @param layerId the id of the layer + * @param sourceId the id of the source + */ + public HeatmapLayer(String layerId, String sourceId) { + initialize(layerId, sourceId); + } + + protected native void initialize(String layerId, String sourceId); + + /** + * Set the source layer. + * + * @param sourceLayer the source layer to set + */ + public void setSourceLayer(String sourceLayer) { + nativeSetSourceLayer(sourceLayer); + } + + /** + * Set the source Layer. + * + * @param sourceLayer the source layer to set + * @return This + */ + public HeatmapLayer withSourceLayer(String sourceLayer) { + setSourceLayer(sourceLayer); + return this; + } + + /** + * Get the source layer. + * + * @return sourceLayer the source layer to get + */ + public String getSourceLayer() { + return nativeGetSourceLayer(); + } + + /** + * Set a single filter. + * + * @param filter the filter to set + */ + public void setFilter(Filter.Statement filter) { + nativeSetFilter(filter.toArray()); + } + + /** + * Set a single filter. + * + * @param filter the filter to set + * @return This + */ + public HeatmapLayer withFilter(Filter.Statement filter) { + setFilter(filter); + return this; + } + + /** + * Set a property or properties. + * + * @param properties the var-args properties + * @return This + */ + public HeatmapLayer withProperties(@NonNull PropertyValue... properties) { + setProperties(properties); + return this; + } + + // Property getters + + /** + * Get the HeatmapRadius property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapRadius() { + return (PropertyValue) new PropertyValue("heatmap-radius", nativeGetHeatmapRadius()); + } + + /** + * Get the HeatmapRadius property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapRadiusTransition() { + return nativeGetHeatmapRadiusTransition(); + } + + /** + * Set the HeatmapRadius property transition options + * + * @param options transition options for Float + */ + public void setHeatmapRadiusTransition(TransitionOptions options) { + nativeSetHeatmapRadiusTransition(options.getDuration(), options.getDelay()); + } + + /** + * Get the HeatmapWeight property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapWeight() { + return (PropertyValue) new PropertyValue("heatmap-weight", nativeGetHeatmapWeight()); + } + + /** + * Get the HeatmapIntensity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapIntensity() { + return (PropertyValue) new PropertyValue("heatmap-intensity", nativeGetHeatmapIntensity()); + } + + /** + * Get the HeatmapIntensity property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapIntensityTransition() { + return nativeGetHeatmapIntensityTransition(); + } + + /** + * Set the HeatmapIntensity property transition options + * + * @param options transition options for Float + */ + public void setHeatmapIntensityTransition(TransitionOptions options) { + nativeSetHeatmapIntensityTransition(options.getDuration(), options.getDelay()); + } + + /** + * Get the HeatmapOpacity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapOpacity() { + return (PropertyValue) new PropertyValue("heatmap-opacity", nativeGetHeatmapOpacity()); + } + + /** + * Get the HeatmapOpacity property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapOpacityTransition() { + return nativeGetHeatmapOpacityTransition(); + } + + /** + * Set the HeatmapOpacity property transition options + * + * @param options transition options for Float + */ + public void setHeatmapOpacityTransition(TransitionOptions options) { + nativeSetHeatmapOpacityTransition(options.getDuration(), options.getDelay()); + } + + private native Object nativeGetHeatmapRadius(); + + private native TransitionOptions nativeGetHeatmapRadiusTransition(); + + private native void nativeSetHeatmapRadiusTransition(long duration, long delay); + + private native Object nativeGetHeatmapWeight(); + + private native Object nativeGetHeatmapIntensity(); + + private native TransitionOptions nativeGetHeatmapIntensityTransition(); + + private native void nativeSetHeatmapIntensityTransition(long duration, long delay); + + private native Object nativeGetHeatmapOpacity(); + + private native TransitionOptions nativeGetHeatmapOpacityTransition(); + + private native void nativeSetHeatmapOpacityTransition(long duration, long delay); + + @Override + protected native void finalize() throws Throwable; + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java index 6e644c5591..18ee05e63b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java @@ -1515,6 +1515,138 @@ public class PropertyFactory { return new PaintPropertyValue<>("circle-stroke-opacity", function); } + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapRadius(Float value) { + return new PaintPropertyValue<>("heatmap-radius", value); + } + + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapRadius(Expression expression) { + return new PaintPropertyValue<>("heatmap-radius", expression); + } + + + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapRadius(Function function) { + return new PaintPropertyValue<>("heatmap-radius", function); + } + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapWeight(Float value) { + return new PaintPropertyValue<>("heatmap-weight", value); + } + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapWeight(Expression expression) { + return new PaintPropertyValue<>("heatmap-weight", expression); + } + + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapWeight(Function function) { + return new PaintPropertyValue<>("heatmap-weight", function); + } + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapIntensity(Float value) { + return new PaintPropertyValue<>("heatmap-intensity", value); + } + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapIntensity(Expression expression) { + return new PaintPropertyValue<>("heatmap-intensity", expression); + } + + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapIntensity(CameraFunction function) { + return new PaintPropertyValue<>("heatmap-intensity", function); + } + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapOpacity(Float value) { + return new PaintPropertyValue<>("heatmap-opacity", value); + } + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapOpacity(Expression expression) { + return new PaintPropertyValue<>("heatmap-opacity", expression); + } + + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapOpacity(CameraFunction function) { + return new PaintPropertyValue<>("heatmap-opacity", function); + } + /** * The opacity of the entire fill extrusion layer. This is rendered on a per-layer, not per-feature, basis, and data-driven styling is not available. * diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java new file mode 100644 index 0000000000..364c8d2679 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java @@ -0,0 +1,626 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +package com.mapbox.mapboxsdk.testapp.style; + +import android.graphics.Color; +import android.support.test.espresso.UiController; +import android.support.test.runner.AndroidJUnit4; + +import timber.log.Timber; + +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.style.functions.CompositeFunction; +import com.mapbox.mapboxsdk.style.functions.CameraFunction; +import com.mapbox.mapboxsdk.style.functions.SourceFunction; +import com.mapbox.mapboxsdk.style.functions.stops.CategoricalStops; +import com.mapbox.mapboxsdk.style.functions.stops.ExponentialStops; +import com.mapbox.mapboxsdk.style.functions.stops.IdentityStops; +import com.mapbox.mapboxsdk.style.functions.stops.IntervalStops; +import com.mapbox.mapboxsdk.style.functions.stops.Stop; +import com.mapbox.mapboxsdk.style.functions.stops.Stops; +import com.mapbox.mapboxsdk.style.layers.HeatmapLayer; +import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction; +import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.mapbox.mapboxsdk.style.functions.Function.*; +import static com.mapbox.mapboxsdk.style.functions.stops.Stop.stop; +import static com.mapbox.mapboxsdk.style.functions.stops.Stops.*; +import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke; +import static org.junit.Assert.*; +import static com.mapbox.mapboxsdk.style.layers.Property.*; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.*; + +import com.mapbox.mapboxsdk.style.layers.TransitionOptions; +import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity; + +/** + * Basic smoke tests for HeatmapLayer + */ +@RunWith(AndroidJUnit4.class) +public class HeatmapLayerTest extends BaseActivityTest { + + private HeatmapLayer layer; + + @Override + protected Class getActivityClass() { + return EspressoTestActivity.class; + } + + private void setupLayer() { + Timber.i("Retrieving layer"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + if ((layer = mapboxMap.getLayerAs("my-layer")) == null) { + Timber.i("Adding layer"); + layer = new HeatmapLayer("my-layer", "composite"); + layer.setSourceLayer("composite"); + mapboxMap.addLayer(layer); + // Layer reference is now stale, get new reference + layer = mapboxMap.getLayerAs("my-layer"); + } + } + }); + } + + @Test + public void testSetVisibility() { + validateTestSetup(); + setupLayer(); + Timber.i("Visibility"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getVisibility().getValue(), VISIBLE); + + // Set + layer.setProperties(visibility(NONE)); + assertEquals(layer.getVisibility().getValue(), NONE); + } + }); + } + + @Test + public void testSourceLayer() { + validateTestSetup(); + setupLayer(); + Timber.i("SourceLayer"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getSourceLayer(), "composite"); + + // Set + final String sourceLayer = "test"; + layer.setSourceLayer(sourceLayer); + assertEquals(layer.getSourceLayer(), sourceLayer); + } + }); + } + + @Test + public void testHeatmapRadiusTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radiusTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapRadiusTransition(options); + assertEquals(layer.getHeatmapRadiusTransition(), options); + } + }); + } + + @Test + public void testHeatmapRadiusAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapRadius(0.3f)); + assertEquals((Float) layer.getHeatmapRadius().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapRadiusAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + zoom( + exponential( + stop(2, heatmapRadius(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapRadiusAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius(property("FeaturePropertyA", Stops.identity())) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapRadiusAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, heatmapRadius(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapRadiusAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, heatmapRadius(0.3f)) + ) + ).withDefaultValue(heatmapRadius(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testHeatmapRadiusAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, heatmapRadius(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(heatmapRadius(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(CompositeFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size()); + + ExponentialStops, Float> stops = + (ExponentialStops, Float>) layer.getHeatmapRadius().getFunction().getStops(); + Stop, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test + public void testHeatmapWeightAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapWeight(0.3f)); + assertEquals((Float) layer.getHeatmapWeight().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapWeightAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + zoom( + exponential( + stop(2, heatmapWeight(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapWeightAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight(property("FeaturePropertyA", Stops.identity())) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapWeightAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, heatmapWeight(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapWeightAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, heatmapWeight(0.3f)) + ) + ).withDefaultValue(heatmapWeight(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testHeatmapWeightAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, heatmapWeight(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(heatmapWeight(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(CompositeFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size()); + + ExponentialStops, Float> stops = + (ExponentialStops, Float>) layer.getHeatmapWeight().getFunction().getStops(); + Stop, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test + public void testHeatmapIntensityTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensityTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapIntensityTransition(options); + assertEquals(layer.getHeatmapIntensityTransition(), options); + } + }); + } + + @Test + public void testHeatmapIntensityAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapIntensity(0.3f)); + assertEquals((Float) layer.getHeatmapIntensity().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapIntensityAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapIntensity( + zoom( + exponential( + stop(2, heatmapIntensity(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapIntensity()); + assertNotNull(layer.getHeatmapIntensity().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapIntensity().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapIntensity().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapOpacityTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacityTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapOpacityTransition(options); + assertEquals(layer.getHeatmapOpacityTransition(), options); + } + }); + } + + @Test + public void testHeatmapOpacityAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapOpacity(0.3f)); + assertEquals((Float) layer.getHeatmapOpacity().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapOpacityAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapOpacity( + zoom( + exponential( + stop(2, heatmapOpacity(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapOpacity()); + assertNotNull(layer.getHeatmapOpacity().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapOpacity().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapOpacity().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).size()); + } + }); + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 2d6efc0d84..5a0493e5bd 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -789,6 +789,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity"/> + + + + { + mapboxMap = map; + addEarthquakeSource(); + addHeatmapLayer(); + addCircleLayer(); + }); + } + + private void addEarthquakeSource() { + try { + mapboxMap.addSource(new GeoJsonSource(EARTHQUAKE_SOURCE_ID, new URL(EARTHQUAKE_SOURCE_URL))); + } catch (MalformedURLException malformedUrlException) { + Timber.e(malformedUrlException, "That's not an url... "); + } + } + + private void addHeatmapLayer() { + HeatmapLayer layer = new HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID); + layer.setMaxZoom(9); + layer.setSourceLayer(HEATMAP_LAYER_SOURCE); + layer.setProperties( + + // TODO add heatmap color https://github.com/mapbox/mapbox-gl-native/issues/11172 + // Color ramp for heatmap. Domain is 0 (low) to 1 (high). + // Begin color ramp at 0-stop with a 0-transparancy color + // to create a blur-like effect. + //heatmapColor(), + + // Increase the heatmap weight based on frequency and property magnitude + heatmapWeight( + interpolate( + linear(), get("mag"), + stop(0, 0), + stop(6, 1) + ) + ), + + // Increase the heatmap color weight weight by zoom level + // heatmap-intensity is a multiplier on top of heatmap-weight + heatmapIntensity( + interpolate( + linear(), zoom(), + stop(0, 1), + stop(9, 3) + ) + ), + + // Adjust the heatmap radius by zoom level + heatmapRadius( + interpolate( + linear(), zoom(), + stop(0, 2), + stop(9, 20) + ) + ), + + // Transition from heatmap to circle layer by zoom level + heatmapOpacity( + interpolate( + linear(), zoom(), + stop(7, 1), + stop(9, 0) + ) + ) + ); + + mapboxMap.addLayerAbove(layer, "waterway-label"); + } + + private void addCircleLayer() { + CircleLayer circleLayer = new CircleLayer(CIRCLE_LAYER_ID, EARTHQUAKE_SOURCE_ID); + circleLayer.setProperties( + + // Size circle radius by earthquake magnitude and zoom level + circleRadius( + interpolate( + linear(), zoom(), + literal(7), interpolate( + linear(), get("mag"), + stop(1, 1), + stop(6, 4) + ), + literal(16), interpolate( + linear(), get("mag"), + stop(1, 5), + stop(6, 50) + ) + ) + ), + + // Color circle by earthquake magnitude + circleColor( + interpolate( + linear(), get("mag"), + literal(1), rgba(33, 102, 172, 0), + literal(2), rgb(103, 169, 207), + literal(3), rgb(209, 229, 240), + literal(4), rgb(253, 219, 199), + literal(5), rgb(239, 138, 98), + literal(6), rgb(178, 24, 43) + ) + ), + + // Transition from heatmap to circle layer by zoom level + circleOpacity( + interpolate( + linear(), zoom(), + stop(7, 0), + stop(8, 1) + ) + ), + circleStrokeColor("white"), + circleStrokeWidth(1.0f) + ); + + mapboxMap.addLayerBelow(circleLayer, HEATMAP_LAYER_ID); + } + + @Override + protected void onStart() { + super.onStart(); + mapView.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + mapView.onStop(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml new file mode 100644 index 0000000000..23ef9ea336 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index e867046c80..d17ec20546 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -70,4 +70,5 @@ Example Custom Geometry Source Suzhou using Droid Sans for Chinese glyphs Example raster-dem source and hillshade layer + Use HeatmapLayer to visualise earthquakes diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 47fee31c0a..0323d7c428 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -70,4 +70,5 @@ Grid Source Local CJK glyph generation Hillshade + Heatmap layer \ No newline at end of file diff --git a/platform/android/config.cmake b/platform/android/config.cmake index e1c36789f5..30182bbc06 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -158,6 +158,8 @@ add_library(mbgl-android STATIC platform/android/src/style/layers/fill_extrusion_layer.hpp platform/android/src/style/layers/fill_layer.cpp platform/android/src/style/layers/fill_layer.hpp + platform/android/src/style/layers/heatmap_layer.cpp + platform/android/src/style/layers/heatmap_layer.hpp platform/android/src/style/layers/hillshade_layer.cpp platform/android/src/style/layers/hillshade_layer.hpp platform/android/src/style/layers/layer.cpp diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js index 65b0a399ac..6e6d3cfa67 100755 --- a/platform/android/scripts/generate-style-code.js +++ b/platform/android/scripts/generate-style-code.js @@ -28,6 +28,9 @@ var layers = Object.keys(spec.layer.type.values).map((type) => { }, []); const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => { + // disabled for now, see https://github.com/mapbox/mapbox-gl-native/issues/11172 + if (name === 'heatmap-color') return memo; + spec[`paint_${type}`][name].name = name; memo.push(spec[`paint_${type}`][name]); return memo; diff --git a/platform/android/src/style/layers/heatmap_layer.cpp b/platform/android/src/style/layers/heatmap_layer.cpp new file mode 100644 index 0000000000..609499ec93 --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.cpp @@ -0,0 +1,134 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +#include "heatmap_layer.hpp" + +#include + +#include "../conversion/property_value.hpp" +#include "../conversion/transition_options.hpp" + +namespace mbgl { +namespace android { + + /** + * Creates an owning peer object (for layers not attached to the map) from the JVM side + */ + HeatmapLayer::HeatmapLayer(jni::JNIEnv& env, jni::String layerId, jni::String sourceId) + : Layer(env, std::make_unique(jni::Make(env, layerId), jni::Make(env, sourceId))) { + } + + /** + * Creates a non-owning peer object (for layers currently attached to the map) + */ + HeatmapLayer::HeatmapLayer(mbgl::Map& map, mbgl::style::HeatmapLayer& coreLayer) + : Layer(map, coreLayer) { + } + + /** + * Creates an owning peer object (for layers not attached to the map) + */ + HeatmapLayer::HeatmapLayer(mbgl::Map& map, std::unique_ptr coreLayer) + : Layer(map, std::move(coreLayer)) { + } + + HeatmapLayer::~HeatmapLayer() = default; + + // Property getters + + jni::Object HeatmapLayer::getHeatmapRadius(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapRadius()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapRadiusTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapRadiusTransition(); + return *convert>(env, options); + } + + void HeatmapLayer::setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as()->HeatmapLayer::setHeatmapRadiusTransition(options); + } + + jni::Object HeatmapLayer::getHeatmapWeight(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapWeight()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapIntensity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapIntensity()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapIntensityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapIntensityTransition(); + return *convert>(env, options); + } + + void HeatmapLayer::setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as()->HeatmapLayer::setHeatmapIntensityTransition(options); + } + + jni::Object HeatmapLayer::getHeatmapOpacity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapOpacity()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapOpacityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapOpacityTransition(); + return *convert>(env, options); + } + + void HeatmapLayer::setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as()->HeatmapLayer::setHeatmapOpacityTransition(options); + } + + + jni::Class HeatmapLayer::javaClass; + + jni::jobject* HeatmapLayer::createJavaPeer(jni::JNIEnv& env) { + static auto constructor = HeatmapLayer::javaClass.template GetConstructor(env); + return HeatmapLayer::javaClass.New(env, constructor, reinterpret_cast(this)); + } + + void HeatmapLayer::registerNative(jni::JNIEnv& env) { + // Lookup the class + HeatmapLayer::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); + + #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + // Register the peer + jni::RegisterNativePeer( + env, HeatmapLayer::javaClass, "nativePtr", + std::make_unique, + "initialize", + "finalize", + METHOD(&HeatmapLayer::getHeatmapRadiusTransition, "nativeGetHeatmapRadiusTransition"), + METHOD(&HeatmapLayer::setHeatmapRadiusTransition, "nativeSetHeatmapRadiusTransition"), + METHOD(&HeatmapLayer::getHeatmapRadius, "nativeGetHeatmapRadius"), + METHOD(&HeatmapLayer::getHeatmapWeight, "nativeGetHeatmapWeight"), + METHOD(&HeatmapLayer::getHeatmapIntensityTransition, "nativeGetHeatmapIntensityTransition"), + METHOD(&HeatmapLayer::setHeatmapIntensityTransition, "nativeSetHeatmapIntensityTransition"), + METHOD(&HeatmapLayer::getHeatmapIntensity, "nativeGetHeatmapIntensity"), + METHOD(&HeatmapLayer::getHeatmapOpacityTransition, "nativeGetHeatmapOpacityTransition"), + METHOD(&HeatmapLayer::setHeatmapOpacityTransition, "nativeSetHeatmapOpacityTransition"), + METHOD(&HeatmapLayer::getHeatmapOpacity, "nativeGetHeatmapOpacity")); + } + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/layers/heatmap_layer.hpp b/platform/android/src/style/layers/heatmap_layer.hpp new file mode 100644 index 0000000000..85f9f0292e --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.hpp @@ -0,0 +1,50 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +#pragma once + +#include "layer.hpp" +#include "../transition_options.hpp" +#include +#include + +namespace mbgl { +namespace android { + +class HeatmapLayer : public Layer { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/layers/HeatmapLayer"; }; + + static jni::Class javaClass; + + static void registerNative(jni::JNIEnv&); + + HeatmapLayer(jni::JNIEnv&, jni::String, jni::String); + + HeatmapLayer(mbgl::Map&, mbgl::style::HeatmapLayer&); + + HeatmapLayer(mbgl::Map&, std::unique_ptr); + + ~HeatmapLayer(); + + // Properties + + jni::Object getHeatmapRadius(jni::JNIEnv&); + void setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object getHeatmapRadiusTransition(jni::JNIEnv&); + + jni::Object getHeatmapWeight(jni::JNIEnv&); + + jni::Object getHeatmapIntensity(jni::JNIEnv&); + void setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object getHeatmapIntensityTransition(jni::JNIEnv&); + + jni::Object getHeatmapOpacity(jni::JNIEnv&); + void setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object getHeatmapOpacityTransition(jni::JNIEnv&); + jni::jobject* createJavaPeer(jni::JNIEnv&); + +}; // class HeatmapLayer + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp index da1550bdb1..29530879a5 100644 --- a/platform/android/src/style/layers/layer.cpp +++ b/platform/android/src/style/layers/layer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/platform/android/src/style/layers/layers.cpp b/platform/android/src/style/layers/layers.cpp index 5d1d1bbcbf..5df689b45d 100644 --- a/platform/android/src/style/layers/layers.cpp +++ b/platform/android/src/style/layers/layers.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include "custom_layer.hpp" #include "fill_extrusion_layer.hpp" #include "fill_layer.hpp" +#include "heatmap_layer.hpp" #include "hillshade_layer.hpp" #include "line_layer.hpp" #include "raster_layer.hpp" @@ -32,6 +34,7 @@ template <> struct PeerType { using Type = android::Back template <> struct PeerType { using Type = android::CircleLayer; }; template <> struct PeerType { using Type = android::FillExtrusionLayer; }; template <> struct PeerType { using Type = android::FillLayer; }; +template <> struct PeerType { using Type = android::HeatmapLayer; }; template <> struct PeerType { using Type = android::HillshadeLayer; }; template <> struct PeerType { using Type = android::LineLayer; }; template <> struct PeerType { using Type = android::RasterLayer; }; @@ -95,6 +98,7 @@ void registerNativeLayers(jni::JNIEnv& env) { CustomLayer::registerNative(env); FillExtrusionLayer::registerNative(env); FillLayer::registerNative(env); + HeatmapLayer::registerNative(env); HillshadeLayer::registerNative(env); LineLayer::registerNative(env); RasterLayer::registerNative(env); diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index a7804ac948..53a668d10b 100755 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -308,14 +308,15 @@ global.propertyDoc = function (propertyName, property, layerType, kind) { doc += '* Predefined functions, including mathematical and string operators\n' + '* Conditional expressions\n' + '* Variable assignments and references to assigned variables\n'; + const inputVariable = property.name === 'heatmap-color' ? '$heatmapDensity' : '$zoomLevel'; if (property["property-function"]) { - doc += '* Interpolation and step functions applied to the `$zoomLevel` variable and/or feature attributes\n'; + doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable and/or feature attributes\n`; } else if (property.function === "interpolated") { - doc += '* Interpolation and step functions applied to the `$zoomLevel` variable\n\n' + + doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable\n\n` + 'This property does not support applying interpolation or step functions to feature attributes.'; } else { - doc += '* Step functions applied to the `$zoomLevel` variable\n\n' + - 'This property does not support applying interpolation functions to the `$zoomLevel` variable or applying interpolation or step functions to feature attributes.'; + doc += `* Step functions applied to the \`${inputVariable}\` variable\n\n` + + `This property does not support applying interpolation functions to the \`${inputVariable}\` variable or applying interpolation or step functions to feature attributes.`; } } return doc; @@ -387,7 +388,7 @@ global.describeValue = function (value, property, layerType) { throw new Error(`No description available for ${value[0]} expression in ${property.name} of ${layerType}.`); } } - + switch (property.type) { case 'boolean': return value ? '`YES`' : '`NO`'; diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json index 12cfa31575..b0c50a06f8 100644 --- a/platform/darwin/scripts/style-spec-overrides-v8.json +++ b/platform/darwin/scripts/style-spec-overrides-v8.json @@ -23,6 +23,9 @@ "circle": { "doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use many-sided regular polygons and configure their appearance using an `MGLFillStyleLayer` object." }, + "heatmap": { + "doc": "An `MGLHeatmapStyleLayer` is a style layer that renders a heatmap.\n\nA heatmap visualizes the spatial distribution of a large, dense set of point data, using color to avoid cluttering the map with individual points at low zoom levels. The points are weighted by an attribute you specify. Use a heatmap style layer in conjunction with point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nConsider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can alternatively cluster the source using the `MGLShapeSourceOptionClustered` option and render the data using an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer`." + }, "raster": { "doc": "An `MGLRasterStyleLayer` is a style layer that renders georeferenced raster imagery on the map, especially raster tiles.\n\nUse a raster style layer to configure the color parameters of raster tiles loaded by an `MGLRasterSource` object or raster images loaded by an `MGLImageSource` object. For example, you could use a raster style layer to render Mapbox Satellite imagery, a raster tile set uploaded to Mapbox Studio, or a raster map authored in TileMill, the classic Mapbox Editor, or Mapbox Studio Classic.\n\nRaster images may also be used as icons or patterns in a style layer. To register an image for use as an icon or pattern, use the `-[MGLStyle setImage:forName:]` method. To configure a point annotation’s image, use the `MGLAnnotationImage` class." }, @@ -79,6 +82,11 @@ "doc": "The base color of this layer. The extrusion's surfaces will be shaded differently based on this color in combination with the `light` settings. If this color is specified with an alpha component, the alpha component will be ignored; use `fill-extrusion-opacity` to set layer opacityco." } }, + "paint_heatmap": { + "heatmap-color": { + "doc": "Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `$heatmapDensity` as input." + } + }, "paint_line": { "line-pattern": { "doc": "Name of image in style images to use for drawing image lines. For seamless patterns, image width must be a factor of two (2, 4, 8, ..., 512)." @@ -114,4 +122,4 @@ "doc": "Distance that the text's anchor is moved from its original placement." } } -} \ No newline at end of file +} diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.h b/platform/darwin/src/MGLHeatmapStyleLayer.h new file mode 100644 index 0000000000..35095fd52e --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.h @@ -0,0 +1,189 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLFoundation.h" +#import "MGLVectorStyleLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + An `MGLHeatmapStyleLayer` is a style layer that renders a heatmap. + + A heatmap visualizes the spatial distribution of a large, dense set of point + data, using color to avoid cluttering the map with individual points at low + zoom levels. The points are weighted by an attribute you specify. Use a heatmap + style layer in conjunction with point or point collection features in vector + tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, + `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` + instances in an `MGLShapeSource` object. + + Consider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or + `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point + data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can + alternatively cluster the source using the `MGLShapeSourceOptionClustered` + option and render the data using an `MGLCircleStyleLayer` or + `MGLSymbolStyleLayer`. + + You can access an existing heatmap style layer using the + `-[MGLStyle layerWithIdentifier:]` method if you know its identifier; + otherwise, find it using the `MGLStyle.layers` property. You can also create a + new heatmap style layer and add it to the style using a method such as + `-[MGLStyle addLayer:]`. + + ### Example + + ```swift + let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) + layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 0, + 6: 1]) + layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 1, + 9: 3]) + mapView.style?.addLayer(layer) + ``` + */ +MGL_EXPORT +@interface MGLHeatmapStyleLayer : MGLVectorStyleLayer + +/** + Returns a heatmap style layer initialized with an identifier and source. + + After initializing and configuring the style layer, add it to a map view’s + style using the `-[MGLStyle addLayer:]` or + `-[MGLStyle insertLayer:belowLayer:]` method. + + @param identifier A string that uniquely identifies the source in the style to + which it is added. + @param source The source from which to obtain the data to style. If the source + has not yet been added to the current style, the behavior is undefined. + @return An initialized foreground style layer. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source; + +#pragma mark - Accessing the Paint Attributes + +/** + Defines the color of each point based on its density value in a heatmap. Should + be an expression that uses `$heatmapDensity` as input. + + The default value of this property is an expression that evaluates to a rainbow + color scale from blue to red. Set this property to `nil` to reset it to the + default value. + + You can set this property to an expression containing any of the following: + + * Constant `UIColor` values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$heatmapDensity` variable + + This property does not support applying interpolation or step functions to + feature attributes. + */ +@property (nonatomic, null_resettable) NSExpression *heatmapColor; + +/** + Similar to `heatmapWeight` but controls the intensity of the heatmap globally. + Primarily used for adjusting the heatmap based on zoom level. + + The default value of this property is an expression that evaluates to the float + `1`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable + + This property does not support applying interpolation or step functions to + feature attributes. + */ +@property (nonatomic, null_resettable) NSExpression *heatmapIntensity; + +/** + The transition affecting any changes to this layer’s `heatmapIntensity` property. + + This property corresponds to the `heatmap-intensity-transition` property in the style JSON file format. +*/ +@property (nonatomic) MGLTransition heatmapIntensityTransition; + +/** + The global opacity at which the heatmap layer will be drawn. + + The default value of this property is an expression that evaluates to the float + `1`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable + + This property does not support applying interpolation or step functions to + feature attributes. + */ +@property (nonatomic, null_resettable) NSExpression *heatmapOpacity; + +/** + The transition affecting any changes to this layer’s `heatmapOpacity` property. + + This property corresponds to the `heatmap-opacity-transition` property in the style JSON file format. +*/ +@property (nonatomic) MGLTransition heatmapOpacityTransition; + +/** + Radius of influence of one heatmap point in points. Increasing the value makes + the heatmap smoother, but less detailed. + + This property is measured in points. + + The default value of this property is an expression that evaluates to the float + `30`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable and/or + feature attributes + */ +@property (nonatomic, null_resettable) NSExpression *heatmapRadius; + +/** + The transition affecting any changes to this layer’s `heatmapRadius` property. + + This property corresponds to the `heatmap-radius-transition` property in the style JSON file format. +*/ +@property (nonatomic) MGLTransition heatmapRadiusTransition; + +/** + A measure of how much an individual point contributes to the heatmap. A value + of 10 would be equivalent to having 10 points of weight 1 in the same spot. + Especially useful when combined with clustering. + + The default value of this property is an expression that evaluates to the float + `1`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable and/or + feature attributes + */ +@property (nonatomic, null_resettable) NSExpression *heatmapWeight; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.mm b/platform/darwin/src/MGLHeatmapStyleLayer.mm new file mode 100644 index 0000000000..a394dbda3b --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.mm @@ -0,0 +1,210 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLSource.h" +#import "NSPredicate+MGLAdditions.h" +#import "NSDate+MGLAdditions.h" +#import "MGLStyleLayer_Private.h" +#import "MGLStyleValue_Private.h" +#import "MGLHeatmapStyleLayer.h" + +#include +#include + +@interface MGLHeatmapStyleLayer () + +@property (nonatomic, readonly) mbgl::style::HeatmapLayer *rawLayer; + +@end + +@implementation MGLHeatmapStyleLayer + +- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source +{ + auto layer = std::make_unique(identifier.UTF8String, source.identifier.UTF8String); + return self = [super initWithPendingLayer:std::move(layer)]; +} + +- (mbgl::style::HeatmapLayer *)rawLayer +{ + return (mbgl::style::HeatmapLayer *)super.rawLayer; +} + +- (NSString *)sourceIdentifier +{ + MGLAssertStyleLayerIsValid(); + + return @(self.rawLayer->getSourceID().c_str()); +} + +- (NSString *)sourceLayerIdentifier +{ + MGLAssertStyleLayerIsValid(); + + auto layerID = self.rawLayer->getSourceLayer(); + return layerID.empty() ? nil : @(layerID.c_str()); +} + +- (void)setSourceLayerIdentifier:(NSString *)sourceLayerIdentifier +{ + MGLAssertStyleLayerIsValid(); + + self.rawLayer->setSourceLayer(sourceLayerIdentifier.UTF8String ?: ""); +} + +- (void)setPredicate:(NSPredicate *)predicate +{ + MGLAssertStyleLayerIsValid(); + + self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter()); +} + +- (NSPredicate *)predicate +{ + MGLAssertStyleLayerIsValid(); + + return [NSPredicate mgl_predicateWithFilter:self.rawLayer->getFilter()]; +} + +#pragma mark - Accessing the Paint Attributes + +- (void)setHeatmapColor:(NSExpression *)heatmapColor { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue(heatmapColor); + self.rawLayer->setHeatmapColor(mbglValue); +} + +- (NSExpression *)heatmapColor { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapColor(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapColor(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapIntensity:(NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapIntensity); + self.rawLayer->setHeatmapIntensity(mbglValue); +} + +- (NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapIntensity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapIntensity(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapIntensityTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapIntensityTransition(options); +} + +- (MGLTransition)heatmapIntensityTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapIntensityTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapOpacity:(NSExpression *)heatmapOpacity { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapOpacity); + self.rawLayer->setHeatmapOpacity(mbglValue); +} + +- (NSExpression *)heatmapOpacity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapOpacity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapOpacity(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapOpacityTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapOpacityTransition(options); +} + +- (MGLTransition)heatmapOpacityTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapOpacityTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapRadius:(NSExpression *)heatmapRadius { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapRadius); + self.rawLayer->setHeatmapRadius(mbglValue); +} + +- (NSExpression *)heatmapRadius { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapRadius(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapRadius(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapRadiusTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapRadiusTransition(options); +} + +- (MGLTransition)heatmapRadiusTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapRadiusTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapWeight:(NSExpression *)heatmapWeight { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapWeight); + self.rawLayer->setHeatmapWeight(mbglValue); +} + +- (NSExpression *)heatmapWeight { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapWeight(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapWeight(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +@end diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 5128944312..f6fc5533be 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -8,6 +8,7 @@ #import "MGLLineStyleLayer.h" #import "MGLCircleStyleLayer.h" #import "MGLSymbolStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLRasterStyleLayer.h" #import "MGLBackgroundStyleLayer.h" @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -401,6 +403,8 @@ static NSURL *MGLStyleURL_trafficNight; return [[MGLSymbolStyleLayer alloc] initWithRawLayer:symbolLayer]; } else if (auto rasterLayer = rawLayer->as()) { return [[MGLRasterStyleLayer alloc] initWithRawLayer:rasterLayer]; + } else if (auto heatmapLayer = rawLayer->as()) { + return [[MGLHeatmapStyleLayer alloc] initWithRawLayer:heatmapLayer]; } else if (auto hillshadeLayer = rawLayer->as()) { return [[MGLHillshadeStyleLayer alloc] initWithRawLayer:hillshadeLayer]; } else if (auto circleLayer = rawLayer->as()) { diff --git a/platform/darwin/src/MGLStyleLayer.mm.ejs b/platform/darwin/src/MGLStyleLayer.mm.ejs index 41b029791f..ac7676a1cc 100644 --- a/platform/darwin/src/MGLStyleLayer.mm.ejs +++ b/platform/darwin/src/MGLStyleLayer.mm.ejs @@ -157,7 +157,9 @@ namespace mbgl { - (void)set<%- camelize(property.name) %>:(NSExpression *)<%- objCName(property) %> { MGLAssertStyleLayerIsValid(); -<% if (property["property-function"]) { -%> +<% if (property.name === 'heatmap-color') { -%> + auto mbglValue = MGLStyleValueTransformer().toPropertyValue(heatmapColor); +<% } else if (property["property-function"]) { -%> auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue>>(<%- objCName(property) %>); <% } else { -%> auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue>>(<%- objCName(property) %>); diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h index ba4b413a3c..5124c29a90 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -10,6 +10,7 @@ #import "MGLConversion.h" #include #include +#include #include #import @@ -52,11 +53,20 @@ public: return mbglValue.evaluate(evaluator); } + // Convert an mbgl heatmap color property value into an mgl style value + NSExpression *toExpression(const mbgl::style::HeatmapColorPropertyValue &mbglValue) { + if (mbglValue.isUndefined()) { + return nil; + } + return [NSExpression mgl_expressionWithJSONObject:MGLJSONObjectFromMBGLExpression(mbglValue.getExpression())]; + } + /** Converts an NSExpression to an mbgl property value. */ template - MBGLValue toPropertyValue(NSExpression *expression) { + typename std::enable_if_t::value, + MBGLValue> toPropertyValue(NSExpression *expression) { if (!expression) { return {}; } @@ -85,6 +95,30 @@ public: return *value; } + + /** + Converts an NSExpression to an mbgl property value. + */ + template + typename std::enable_if_t::value, + MBGLValue> toPropertyValue(NSExpression *expression) { + if (!expression) { + return {}; + } + + NSArray *jsonExpression = expression.mgl_jsonExpressionObject; + + mbgl::style::conversion::Error valueError; + auto value = mbgl::style::conversion::convert( + mbgl::style::conversion::makeConvertible(jsonExpression), valueError); + if (!value) { + [NSException raise:NSInvalidArgumentException + format:@"Invalid property value: %@", @(valueError.message.c_str())]; + return {}; + } + + return *value; + } private: // Private utilities for converting from mgl to mbgl values diff --git a/platform/darwin/src/MGLVectorStyleLayer.h b/platform/darwin/src/MGLVectorStyleLayer.h index 7780a34c7f..177b1b70f0 100644 --- a/platform/darwin/src/MGLVectorStyleLayer.h +++ b/platform/darwin/src/MGLVectorStyleLayer.h @@ -10,10 +10,10 @@ NS_ASSUME_NONNULL_BEGIN is defined by an `MGLShapeSource` or `MGLVectorSource` object. Create instances of `MGLCircleStyleLayer`, `MGLFillStyleLayer`, - `MGLFillExtrusionStyleLayer`, `MGLLineStyleLayer`, and `MGLSymbolStyleLayer` in - order to use `MGLVectorStyleLayer`'s properties and methods. Do not create - instances of `MGLVectorStyleLayer` directly, and do not create your own - subclasses of this class. + `MGLFillExtrusionStyleLayer`, `MGLHeatmapStyleLayer`, `MGLLineStyleLayer`, and + `MGLSymbolStyleLayer` in order to use `MGLVectorStyleLayer`'s properties and + methods. Do not create instances of `MGLVectorStyleLayer` directly, and do not + create your own subclasses of this class. */ MGL_EXPORT @interface MGLVectorStyleLayer : MGLForegroundStyleLayer diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 9bf9924869..5a6e00bc4e 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -230,6 +230,24 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.layer(withIdentifier: "buildings")) } + + func testMGLHeatmapStyleLayer() { + let earthquakes = MGLShapeSource(identifier: "earthquakes", url: URL(string: "https://example.com/earthquakes.json")!, options: [:]) + mapView.style?.addSource(earthquakes) + + //#-example-code + let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) + layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 0, + 6: 1]) + layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 1, + 9: 3]) + mapView.style?.addLayer(layer) + //#-end-example-code + + XCTAssertNotNil(mapView.style?.layer(withIdentifier: "earthquake-heat")) + } func testMGLSymbolStyleLayer() { let pois = MGLVectorSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!) diff --git a/platform/darwin/test/MGLHeatmapColorTests.mm b/platform/darwin/test/MGLHeatmapColorTests.mm new file mode 100644 index 0000000000..8d44064d94 --- /dev/null +++ b/platform/darwin/test/MGLHeatmapColorTests.mm @@ -0,0 +1,61 @@ +#import +#import + +#import "MGLStyleLayer_Private.h" + +#include + +@interface MGLHeatmapColorTests : XCTestCase +@end + +@implementation MGLHeatmapColorTests + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + auto rawLayer = layer.rawLayer->as(); + + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"heatmap-color should be unset initially."); + NSExpression *defaultExpression = layer.heatmapColor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.heatmapColor = constantExpression; + + + mbgl::style::PropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), + @"Setting heatmapColor to a constant value expression should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, constantExpression, + @"heatmapColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MGLColor blueColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($heatmapDensity, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@12: constantExpression2}]; + layer.heatmapColor = functionExpression; + + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, functionExpression, + @"heatmapColor should round-trip expressions depending on $heatmapDensity."); + + layer.heatmapColor = nil; + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"Unsetting heatmapColor should return heatmap-color to the default value."); + XCTAssertEqualObjects(layer.heatmapColor, defaultExpression, + @"heatmapColor should return the default value after being unset."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a data expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); +} + +@end diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm new file mode 100644 index 0000000000..74121affd8 --- /dev/null +++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm @@ -0,0 +1,296 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLStyleLayerTests.h" +#import "../../darwin/src/NSDate+MGLAdditions.h" + +#import "MGLStyleLayer_Private.h" + +#include +#include + +@interface MGLHeatmapLayerTests : MGLStyleLayerTests +@end + +@implementation MGLHeatmapLayerTests + ++ (NSString *)layerType { + return @"heatmap"; +} + +- (void)testPredicates { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + XCTAssertNil(layer.sourceLayerIdentifier); + layer.sourceLayerIdentifier = @"layerID"; + XCTAssertEqualObjects(layer.sourceLayerIdentifier, @"layerID"); + layer.sourceLayerIdentifier = nil; + XCTAssertNil(layer.sourceLayerIdentifier); + + XCTAssertNil(layer.predicate); + layer.predicate = [NSPredicate predicateWithValue:NO]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = nil; + XCTAssertNil(layer.predicate); +} + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + XCTAssertNotEqual(layer.rawLayer, nullptr); + XCTAssertTrue(layer.rawLayer->is()); + auto rawLayer = layer.rawLayer->as(); + + MGLTransition transitionTest = MGLTransitionMake(5, 4); + + + // heatmap-intensity + { + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"heatmap-intensity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapIntensity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapIntensity = constantExpression; + mbgl::style::PropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a constant value expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, constantExpression, + @"heatmapIntensity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapIntensity = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a camera expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, functionExpression, + @"heatmapIntensity should round-trip camera expressions."); + + + + layer.heatmapIntensity = nil; + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"Unsetting heatmapIntensity should return heatmap-intensity to the default value."); + XCTAssertEqualObjects(layer.heatmapIntensity, defaultExpression, + @"heatmapIntensity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapIntensityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapIntensityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapIntensityTransition = layer.heatmapIntensityTransition; + XCTAssertEqual(heatmapIntensityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapIntensityTransition.duration, transitionTest.duration); + } + + // heatmap-opacity + { + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"heatmap-opacity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapOpacity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapOpacity = constantExpression; + mbgl::style::PropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a constant value expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, constantExpression, + @"heatmapOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapOpacity = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a camera expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, functionExpression, + @"heatmapOpacity should round-trip camera expressions."); + + + + layer.heatmapOpacity = nil; + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"Unsetting heatmapOpacity should return heatmap-opacity to the default value."); + XCTAssertEqualObjects(layer.heatmapOpacity, defaultExpression, + @"heatmapOpacity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapOpacityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapOpacityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapOpacityTransition = layer.heatmapOpacityTransition; + XCTAssertEqual(heatmapOpacityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapOpacityTransition.duration, transitionTest.duration); + } + + // heatmap-radius + { + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"heatmap-radius should be unset initially."); + NSExpression *defaultExpression = layer.heatmapRadius; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapRadius = constantExpression; + mbgl::style::DataDrivenPropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a constant value expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, constantExpression, + @"heatmapRadius should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::ExponentialStops exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a data expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapRadius = functionExpression; + + std::map innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera-data expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip camera-data expressions."); + + + layer.heatmapRadius = nil; + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"Unsetting heatmapRadius should return heatmap-radius to the default value."); + XCTAssertEqualObjects(layer.heatmapRadius, defaultExpression, + @"heatmapRadius should return the default value after being unset."); + // Transition property test + layer.heatmapRadiusTransition = transitionTest; + auto toptions = rawLayer->getHeatmapRadiusTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapRadiusTransition = layer.heatmapRadiusTransition; + XCTAssertEqual(heatmapRadiusTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapRadiusTransition.duration, transitionTest.duration); + } + + // heatmap-weight + { + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"heatmap-weight should be unset initially."); + NSExpression *defaultExpression = layer.heatmapWeight; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapWeight = constantExpression; + mbgl::style::DataDrivenPropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a constant value expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, constantExpression, + @"heatmapWeight should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::ExponentialStops exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a data expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapWeight = functionExpression; + + std::map innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera-data expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip camera-data expressions."); + + + layer.heatmapWeight = nil; + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"Unsetting heatmapWeight should return heatmap-weight to the default value."); + XCTAssertEqualObjects(layer.heatmapWeight, defaultExpression, + @"heatmapWeight should return the default value after being unset."); + } +} + +- (void)testPropertyNames { + [self testPropertyName:@"heatmap-intensity" isBoolean:NO]; + [self testPropertyName:@"heatmap-opacity" isBoolean:NO]; + [self testPropertyName:@"heatmap-radius" isBoolean:NO]; + [self testPropertyName:@"heatmap-weight" isBoolean:NO]; +} + +@end diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs index e26c63e662..e17501ed18 100644 --- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs @@ -59,6 +59,7 @@ MGLTransition transitionTest = MGLTransitionMake(5, 4); <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> // <%- originalPropertyName(property) %> { @@ -151,6 +152,7 @@ - (void)testPropertyNames { <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> [self testPropertyName:@"<%- property.getter || property.name %>" isBoolean:<%- property.type === 'boolean' ? 'YES' : 'NO' %>]; <% } -%> } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index ef27596d2b..d8b89f4d80 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -10,6 +10,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Styles and rendering +* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046) * The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726)) * Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983)) * A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642)) diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md index 72d6f144a0..68d59fbc70 100644 --- a/platform/ios/docs/guides/For Style Authors.md +++ b/platform/ios/docs/guides/For Style Authors.md @@ -192,6 +192,7 @@ In style JSON | In the SDK `circle` | `MGLCircleStyleLayer` `fill` | `MGLFillStyleLayer` `fill-extrusion` | `MGLFillExtrusionStyleLayer` +`heatmap` | `MGLHeatmapStyleLayer` `hillshade` | `MGLHillshadeStyleLayer` `line` | `MGLLineStyleLayer` `raster` | `MGLRasterStyleLayer` diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index e177a9bc87..07fae5945c 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ 16376B471FFDB92B0000563E /* one-liner.json in Resources */ = {isa = PBXBuildFile; fileRef = DA35D0871E1A6309007DED41 /* one-liner.json */; }; 16376B491FFEED010000563E /* MGLMapViewLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */; }; 165D0CE720005419009A3C66 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; }; + 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */; }; + 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */; }; 1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -226,6 +228,10 @@ 55E2AD131E5B125400E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD121E5B125400E8C587 /* MGLOfflineStorageTests.mm */; }; 632281DF1E6F855900D75A5D /* MBXEmbeddedMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */; }; 6407D6701E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */; }; + 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; }; + 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; }; 920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */; }; 927FBCFC1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */; }; 927FBCFF1F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -670,6 +676,8 @@ 16376B3F1FFDB4B40000563E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 16376B401FFDB4B40000563E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewLayoutTests.m; sourceTree = ""; }; + 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapColorTests.mm; path = ../../darwin/test/MGLHeatmapColorTests.mm; sourceTree = ""; }; + 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapStyleLayerTests.mm; path = ../../darwin/test/MGLHeatmapStyleLayerTests.mm; sourceTree = ""; }; 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; 1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = ""; }; @@ -801,6 +809,8 @@ 632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXEmbeddedMapViewController.h; sourceTree = ""; }; 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXEmbeddedMapViewController.m; sourceTree = ""; }; 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationExampleTests.swift; path = ../../darwin/test/MGLDocumentationExampleTests.swift; sourceTree = ""; }; + 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = ""; }; + 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = ""; }; 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = ""; }; 927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXSnapshotsViewController.h; sourceTree = ""; }; 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXSnapshotsViewController.m; sourceTree = ""; }; @@ -1236,6 +1246,8 @@ FA68F1481E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.h */, FA68F1491E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.mm */, 35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */, + 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */, + 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */, 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */, 3538AA1B1D542239008EC33D /* MGLForegroundStyleLayer.h */, 3538AA1C1D542239008EC33D /* MGLForegroundStyleLayer.mm */, @@ -1306,6 +1318,8 @@ 3575798F1D513EF1000B822E /* Layers */ = { isa = PBXGroup; children = ( + 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */, + 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */, DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */, DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */, DA3C6FF21E2859E700F962BE /* test-Bridging-Header.h */, @@ -1967,6 +1981,7 @@ DA8847F91CBAFA5100AB86E3 /* MGLPolygon.h in Headers */, 4049C2AC1DB6E05500B3F799 /* MGLPointCollection_Private.h in Headers */, DA8847F81CBAFA5100AB86E3 /* MGLPointAnnotation.h in Headers */, + 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */, 353933F21D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */, DA8847F31CBAFA5100AB86E3 /* MGLMultiPoint.h in Headers */, 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */, @@ -2104,6 +2119,7 @@ DABFB8631CBE99E500D62B32 /* MGLOfflineRegion.h in Headers */, DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */, DAF0D8141DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */, + 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */, DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */, 357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */, 1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */, @@ -2548,6 +2564,8 @@ 409F43FD1E9E781C0048729D /* MGLMapViewDelegateIntegrationTests.swift in Sources */, DA2E88651CC0382C00F24E7B /* MGLStyleTests.mm in Sources */, DA2E88611CC0382C00F24E7B /* MGLGeometryTests.mm in Sources */, + 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */, + 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */, 357579801D501E09000B822E /* MGLFillStyleLayerTests.mm in Sources */, 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */, DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */, @@ -2660,6 +2678,7 @@ 35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */, DA8848521CBAFB9800AB86E3 /* MGLAPIClient.m in Sources */, 966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */, + 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */, DA8848301CBAFA6200AB86E3 /* NSProcessInfo+MGLAdditions.m in Sources */, 353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */, 35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */, @@ -2752,6 +2771,7 @@ DAA4E4311CBB730400178DFB /* MGLMapboxEvents.m in Sources */, 966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */, DAA4E4231CBB730400178DFB /* MGLPolygon.mm in Sources */, + 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */, 353AFA171D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */, 35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */, DAA4E42A1CBB730400178DFB /* NSProcessInfo+MGLAdditions.m in Sources */, diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index b3662adc70..61e9ad39e8 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -95,6 +95,7 @@ custom_categories: - MGLCircleStyleLayer - MGLFillStyleLayer - MGLFillExtrusionStyleLayer + - MGLHeatmapStyleLayer - MGLHillshadeStyleLayer - MGLLineStyleLayer - MGLSymbolStyleLayer diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index d105189bb8..11720ac68e 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -45,6 +45,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLSymbolStyleLayer.h" #import "MGLRasterStyleLayer.h" #import "MGLCircleStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLBackgroundStyleLayer.h" #import "MGLOpenGLStyleLayer.h" diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 0c8c59700d..1f671cb82a 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -4,6 +4,7 @@ ### Styles and rendering +* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046) * The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726)) * Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983)) * A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642)) diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json new file mode 100644 index 0000000000..10226ca23a --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "heatmap.pdf", + "language-direction" : "left-to-right" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf new file mode 100644 index 0000000000..88195c3735 Binary files /dev/null and b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf differ diff --git a/platform/macos/app/StyleLayerIconTransformer.m b/platform/macos/app/StyleLayerIconTransformer.m index 50fe06a2c6..199de86d8b 100644 --- a/platform/macos/app/StyleLayerIconTransformer.m +++ b/platform/macos/app/StyleLayerIconTransformer.m @@ -31,6 +31,9 @@ if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) { return [NSImage imageNamed:@"symbol"]; } + if ([layer isKindOfClass:[MGLHeatmapStyleLayer class]]) { + return [NSImage imageNamed:@"heatmap"]; + } if ([layer isKindOfClass:[MGLHillshadeStyleLayer class]]) { return [NSImage imageNamed:@"hillshade"]; } diff --git a/platform/macos/app/heatmap.json b/platform/macos/app/heatmap.json new file mode 100644 index 0000000000..6469e57022 --- /dev/null +++ b/platform/macos/app/heatmap.json @@ -0,0 +1,809 @@ +{ + "version": 8, + "name": "Basic Heatmap", + "center": [ + 30.49860107152665, + 50.459868549177486 + ], + "zoom": 14.033276876197775, + "bearing": 0, + "pitch": 0, + "sources": { + "mapbox": { + "url": "mapbox://mapbox.mapbox-streets-v7", + "type": "vector" + } + }, + "sprite": "mapbox://sprites/mourner/cjcgg2bl16cf42snvcbbaf09z", + "glyphs": "mapbox://fonts/mourner/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "#dedede" + } + }, + { + "id": "landuse_overlay_national_park", + "type": "fill", + "source": "mapbox", + "source-layer": "landuse_overlay", + "filter": [ + "==", + "class", + "national_park" + ], + "paint": { + "fill-color": "#d2edae", + "fill-opacity": 0.75 + } + }, + { + "id": "landuse_park", + "type": "fill", + "source": "mapbox", + "source-layer": "landuse", + "filter": [ + "==", + "class", + "park" + ], + "paint": { + "fill-color": "#d2edae" + } + }, + { + "id": "waterway", + "type": "line", + "source": "mapbox", + "source-layer": "waterway", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "in", + "class", + "canal", + "river" + ] + ], + "paint": { + "line-color": "#a0cfdf", + "line-width": { + "base": 1.4, + "stops": [ + [ + 8, + 0.5 + ], + [ + 20, + 15 + ] + ] + } + } + }, + { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "#a0cfdf" + } + }, + { + "id": "building", + "type": "fill", + "source": "mapbox", + "source-layer": "building", + "paint": { + "fill-color": "#d6d6d6" + } + }, + { + "id": "tunnel_minor", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "tunnel" + ], + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#efefef", + "line-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + }, + "line-dasharray": [ + 0.36, + 0.18 + ] + } + }, + { + "id": "tunnel_major", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "tunnel" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.4, + "stops": [ + [ + 6, + 0.5 + ], + [ + 20, + 30 + ] + ] + }, + "line-dasharray": [ + 0.28, + 0.14 + ] + } + }, + { + "id": "road_minor", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ], + [ + "in", + "structure", + "ford", + "none" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#efefef", + "line-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "road_major", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ], + [ + "in", + "structure", + "ford", + "none" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.4, + "stops": [ + [ + 6, + 0.5 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_minor case", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#dedede", + "line-width": { + "base": 1.6, + "stops": [ + [ + 12, + 0.5 + ], + [ + 20, + 10 + ] + ] + }, + "line-gap-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_major case", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#dedede", + "line-width": { + "base": 1.6, + "stops": [ + [ + 12, + 0.5 + ], + [ + 20, + 10 + ] + ] + }, + "line-gap-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_minor", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#efefef", + "line-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_major", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.4, + "stops": [ + [ + 6, + 0.5 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "admin_country", + "type": "line", + "source": "mapbox", + "source-layer": "admin", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "<=", + "admin_level", + 2 + ], + [ + "==", + "maritime", + 0 + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#8b8a8a", + "line-width": { + "base": 1.3, + "stops": [ + [ + 3, + 0.5 + ], + [ + 22, + 15 + ] + ] + } + } + }, + { + "id": "road_major_label", + "type": "symbol", + "source": "mapbox", + "source-layer": "road_label", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ], + "layout": { + "symbol-placement": "line", + "text-field": "{name_en}", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-transform": "uppercase", + "text-letter-spacing": 0.1, + "text-size": { + "base": 1.4, + "stops": [ + [ + 10, + 8 + ], + [ + 20, + 14 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 2 + } + }, + { + "id": "place_label_other", + "type": "symbol", + "source": "mapbox", + "source-layer": "place_label", + "minzoom": 8, + "filter": [ + "all", + [ + "==", + "$type", + "Point" + ], + [ + "in", + "type", + "hamlet", + "island", + "neighbourhood", + "suburb", + "town", + "village" + ] + ], + "layout": { + "text-field": "{name_en}", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-max-width": 6, + "text-size": { + "stops": [ + [ + 6, + 12 + ], + [ + 12, + 16 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 1, + "text-halo-blur": 1 + } + }, + { + "id": "place_label_city", + "type": "symbol", + "source": "mapbox", + "source-layer": "place_label", + "maxzoom": 16, + "filter": [ + "all", + [ + "==", + "$type", + "Point" + ], + [ + "==", + "type", + "city" + ] + ], + "layout": { + "text-field": "{name_en}", + "text-font": [ + "Open Sans Bold", + "Arial Unicode MS Bold" + ], + "text-max-width": 10, + "text-size": { + "stops": [ + [ + 3, + 12 + ], + [ + 8, + 16 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 1, + "text-halo-blur": 1 + } + }, + { + "id": "country_label", + "type": "symbol", + "source": "mapbox", + "source-layer": "country_label", + "maxzoom": 12, + "filter": [ + "==", + "$type", + "Point" + ], + "layout": { + "text-field": "{name_en}", + "text-font": [ + "Open Sans Regular", + "Arial Unicode MS Regular" + ], + "text-max-width": 10, + "text-size": { + "stops": [ + [ + 3, + 14 + ], + [ + 8, + 22 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 1, + "text-halo-blur": 1 + } + }, + { + "id": "road-heatmap", + "type": "heatmap", + "source": "mapbox", + "source-layer": "road", + "paint": { + "heatmap-intensity": 1 + } + } + ] +} diff --git a/platform/macos/app/resources/heatmap.svg b/platform/macos/app/resources/heatmap.svg new file mode 100644 index 0000000000..fa2a46590a --- /dev/null +++ b/platform/macos/app/resources/heatmap.svg @@ -0,0 +1,72 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index 6897d640b3..de838cd78f 100644 --- a/platform/macos/docs/guides/For Style Authors.md +++ b/platform/macos/docs/guides/For Style Authors.md @@ -179,6 +179,7 @@ In style JSON | In the SDK `circle` | `MGLCircleStyleLayer` `fill` | `MGLFillStyleLayer` `fill-extrusion` | `MGLFillExtrusionStyleLayer` +`heatmap` | `MGLHeatmapStyleLayer` `hillshade` | `MGLHillshadeStyleLayer` `line` | `MGLLineStyleLayer` `raster` | `MGLRasterStyleLayer` diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml index 682ccd99ff..b4dccb620f 100644 --- a/platform/macos/jazzy.yml +++ b/platform/macos/jazzy.yml @@ -80,6 +80,7 @@ custom_categories: - MGLCircleStyleLayer - MGLFillStyleLayer - MGLFillExtrusionStyleLayer + - MGLHeatmapStyleLayer - MGLHillshadeStyleLayer - MGLLineStyleLayer - MGLSymbolStyleLayer diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 090902fca4..4327670911 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 07D9474D1F67441B00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */; }; 07F8E2F71F674C8800F794BB /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 07F8E2F81F674C9000F794BB /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */; }; + 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */; }; + 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */; }; 1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */; }; 1F7454A31ECFB00300021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */; }; 1F7454A41ECFB00300021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A11ECFB00300021D39 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -85,6 +87,9 @@ 55D120A31F7906E6004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; }; 55D120A51F790A0C004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; }; 55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */; }; + 89462399200D199100DA8EF2 /* heatmap.json in Resources */ = {isa = PBXBuildFile; fileRef = 89462398200D199100DA8EF2 /* heatmap.json */; }; + 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */; }; 92092EF01F5EB10E00AF5130 /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92092EF11F5EB10E00AF5130 /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */; }; 920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */; }; @@ -298,6 +303,8 @@ 07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = ""; }; 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = ""; }; 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = ""; }; + 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayerTests.mm; sourceTree = ""; }; + 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapColorTests.mm; sourceTree = ""; }; 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = ""; }; 1F7454A11ECFB00300021D39 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; @@ -369,6 +376,9 @@ 55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflineStorageTests.mm; path = ../../darwin/test/MGLOfflineStorageTests.mm; sourceTree = ""; }; 55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = ""; }; + 89462398200D199100DA8EF2 /* heatmap.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = heatmap.json; sourceTree = ""; }; + 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = ""; }; + 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = ""; }; 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = ""; }; 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = ""; }; 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLSourceQueryTests.m; sourceTree = ""; }; @@ -672,6 +682,8 @@ 35602BF91D3EA99F0050646F /* MGLFillStyleLayer.mm */, 35602BFD1D3EA9B40050646F /* MGLForegroundStyleLayer.h */, 35602BFE1D3EA9B40050646F /* MGLForegroundStyleLayer.mm */, + 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */, + 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */, DAF25714201901C200367EF5 /* MGLHillshadeStyleLayer.h */, DAF25713201901C100367EF5 /* MGLHillshadeStyleLayer.mm */, DA8F25891D51CA540010E6B5 /* MGLLineStyleLayer.h */, @@ -825,6 +837,7 @@ DA839EA61CC2E3400062CAFB /* Info.plist */, 96E027331E57C9A7004B8E66 /* Localizable.strings */, DA839E981CC2E3400062CAFB /* Supporting Files */, + 89462398200D199100DA8EF2 /* heatmap.json */, ); name = "Demo App"; path = app; @@ -875,6 +888,8 @@ DA8F257C1D51C5F40010E6B5 /* Layers */ = { isa = PBXGroup; children = ( + 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */, + 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */, 40E1601A1DF216E6005EA6D9 /* MGLStyleLayerTests.h */, 40E1601B1DF216E6005EA6D9 /* MGLStyleLayerTests.m */, DA2207BA1DC076930002F84D /* test-Bridging-Header.h */, @@ -1232,6 +1247,7 @@ 359819591E02F611008FC139 /* NSCoder+MGLAdditions.h in Headers */, DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */, DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */, + 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */, DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */, DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */, DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */, @@ -1422,6 +1438,7 @@ DA839EA01CC2E3400062CAFB /* MapDocument.xib in Resources */, 353BAEF81D6463B8009A8DA9 /* amsterdam.geojson in Resources */, 96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */, + 89462399200D199100DA8EF2 /* heatmap.json in Resources */, DA839EA51CC2E3400062CAFB /* MainMenu.xib in Resources */, DA5589771D320C41006B7F64 /* wms.json in Resources */, DAE6C2E21CC304F900DB3429 /* Credits.rtf in Resources */, @@ -1503,6 +1520,7 @@ 07D9474B1F6743F000E37934 /* MGLAbstractShapeSource.mm in Sources */, DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */, DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */, + 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */, 3527428E1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.mm in Sources */, 35602C011D3EA9B40050646F /* MGLForegroundStyleLayer.mm in Sources */, 408AA86A1DAEEE5D00022900 /* NSDictionary+MGLAdditions.mm in Sources */, @@ -1565,11 +1583,13 @@ DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */, DA87A9A41DCACC5000810D09 /* MGLSymbolStyleLayerTests.mm in Sources */, 40E1601D1DF217D6005EA6D9 /* MGLStyleLayerTests.m in Sources */, + 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */, 1F95931B1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm in Sources */, DAF25721201902C100367EF5 /* MGLHillshadeStyleLayerTests.mm in Sources */, DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.mm in Sources */, DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */, DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */, + 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */, 4031ACFC1E9EB3C100A3EA26 /* MGLMapViewDelegateIntegrationTests.swift in Sources */, 4031AD031E9FD6AA00A3EA26 /* MGLSDKTestHelpers.swift in Sources */, DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.mm in Sources */, diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 7ea9a9dd1d..0e4b546cf7 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -44,6 +44,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLRasterStyleLayer.h" #import "MGLCircleStyleLayer.h" #import "MGLBackgroundStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLOpenGLStyleLayer.h" #import "MGLSource.h" diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 3e66aaa789..ac14df0228 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index 4d6f26b056..42751da30f 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -23,24 +23,6 @@ "render-tests/fill-extrusion-pattern/opacity": "https://github.com/mapbox/mapbox-gl-js/issues/3327", "render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary", "render-tests/geojson/inline-polygon-symbol": "behavior needs reconciliation with gl-js", - "render-tests/heatmap-color/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-color/expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-color/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-intensity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-intensity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-intensity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-opacity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-opacity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-opacity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/antimeridian": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/pitch30": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-weight/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-weight/identity-property-function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-weight/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", "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", "render-tests/regressions/mapbox-gl-js#2305": "https://github.com/mapbox/mapbox-gl-native/issues/6927", diff --git a/scripts/generate-shaders.js b/scripts/generate-shaders.js index 46c097d51a..b1eeffb8a0 100755 --- a/scripts/generate-shaders.js +++ b/scripts/generate-shaders.js @@ -7,9 +7,6 @@ const outputPath = 'src/mbgl/shaders'; var shaders = require('../mapbox-gl-js/src/shaders'); -delete shaders.heatmap; -delete shaders.heatmapTexture; - require('./style-code'); writeIfModified(path.join(outputPath, 'preludes.hpp'), `// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index ff40244f98..6ddb787f19 100755 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -97,6 +97,8 @@ global.paintPropertyType = function (property, type) { global.propertyValueType = function (property) { if (isDataDriven(property)) { return `DataDrivenPropertyValue<${evaluatedType(property)}>`; + } else if (property.name === 'heatmap-color') { + return `HeatmapColorPropertyValue`; } else { return `PropertyValue<${evaluatedType(property)}>`; } diff --git a/scripts/style-spec.js b/scripts/style-spec.js index 00daee70d0..196adc0b32 100644 --- a/scripts/style-spec.js +++ b/scripts/style-spec.js @@ -1,5 +1,3 @@ var spec = module.exports = require('../mapbox-gl-js/src/style-spec/reference/v8'); // Make temporary modifications here when Native doesn't have all features that JS has. - -delete spec.layer.type.values.heatmap; diff --git a/src/mbgl/gl/color_mode.hpp b/src/mbgl/gl/color_mode.hpp index c6594a3a77..e394f43501 100644 --- a/src/mbgl/gl/color_mode.hpp +++ b/src/mbgl/gl/color_mode.hpp @@ -85,6 +85,10 @@ public: static ColorMode alphaBlended() { return ColorMode { Add { One, OneMinusSrcAlpha }, {}, { true, true, true, true } }; } + + static ColorMode additive() { + return ColorMode { Add { One, One }, {}, { true, true, true, true } }; + } }; constexpr bool operator!=(const ColorMode::Mask& a, const ColorMode::Mask& b) { diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index b6244d58c7..f40cfa1f2c 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -60,6 +60,16 @@ static_assert(std::is_same, GLenum>::value static_assert(underlying_type(TextureFormat::RGBA) == GL_RGBA, "OpenGL type mismatch"); static_assert(underlying_type(TextureFormat::Alpha) == GL_ALPHA, "OpenGL type mismatch"); +static_assert(std::is_same, GLenum>::value, "OpenGL type mismatch"); +static_assert(underlying_type(TextureType::UnsignedByte) == GL_UNSIGNED_BYTE, "OpenGL type mismatch"); + +#if MBGL_USE_GLES2 && GL_HALF_FLOAT_OES +static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_OES, "OpenGL type mismatch"); +#endif +#if !MBGL_USE_GLES2 && GL_HALF_FLOAT_ARB +static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_ARB, "OpenGL type mismatch"); +#endif + static_assert(underlying_type(UniformDataType::Float) == GL_FLOAT, "OpenGL type mismatch"); static_assert(underlying_type(UniformDataType::FloatVec2) == GL_FLOAT_VEC2, "OpenGL type mismatch"); static_assert(underlying_type(UniformDataType::FloatVec3) == GL_FLOAT_VEC3, "OpenGL type mismatch"); @@ -116,6 +126,19 @@ void Context::initializeExtensions(const std::function(fn); #endif +#if MBGL_USE_GLES2 + constexpr const char* halfFloatExtensionName = "OES_texture_half_float"; + constexpr const char* halfFloatColorBufferExtensionName = "EXT_color_buffer_half_float"; +#else + constexpr const char* halfFloatExtensionName = "ARB_half_float_pixel"; + constexpr const char* halfFloatColorBufferExtensionName = "ARB_color_buffer_float"; +#endif + if (strstr(extensions, halfFloatExtensionName) != nullptr && + strstr(extensions, halfFloatColorBufferExtensionName) != nullptr) { + + supportsHalfFloatTextures = true; + } + if (!supportsVertexArrays()) { Log::Warning(Event::OpenGL, "Not using Vertex Array Objects"); } @@ -491,10 +514,10 @@ Context::createFramebuffer(const Texture& color, } UniqueTexture -Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit) { +Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) { auto obj = createTexture(); pixelStoreUnpack = { 1 }; - updateTexture(obj, size, data, format, unit); + updateTexture(obj, size, data, format, unit, type); // We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures. // We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus. MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); @@ -505,11 +528,11 @@ Context::createTexture(const Size size, const void* data, TextureFormat format, } void Context::updateTexture( - TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit) { + TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) { activeTextureUnit = unit; texture[unit] = id; MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, static_cast(format), size.width, - size.height, 0, static_cast(format), GL_UNSIGNED_BYTE, + size.height, 0, static_cast(format), static_cast(type), data)); } diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 14f078367f..67624288e2 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -125,23 +125,29 @@ public: // Create a texture from an image with data. template - Texture createTexture(const Image& image, TextureUnit unit = 0) { + Texture createTexture(const Image& image, + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - return { image.size, createTexture(image.size, image.data.get(), format, unit) }; + return { image.size, createTexture(image.size, image.data.get(), format, unit, type) }; } template - void updateTexture(Texture& obj, const Image& image, TextureUnit unit = 0) { + void updateTexture(Texture& obj, + const Image& image, + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit); + updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit, type); obj.size = image.size; } // Creates an empty texture with the specified dimensions. Texture createTexture(const Size size, TextureFormat format = TextureFormat::RGBA, - TextureUnit unit = 0) { - return { size, createTexture(size, nullptr, format, unit) }; + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { + return { size, createTexture(size, nullptr, format, unit, type) }; } void bindTexture(Texture&, @@ -232,6 +238,8 @@ public: State pixelTransferStencil; #endif // MBGL_USE_GLES2 + bool supportsHalfFloatTextures = false; + private: State stencilFunc; State stencilMask; @@ -259,8 +267,8 @@ private: void updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); UniqueBuffer createIndexBuffer(const void* data, std::size_t size, const BufferUsage usage); void updateIndexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); - UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit); - void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit); + UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit, TextureType); + void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit, TextureType); UniqueFramebuffer createFramebuffer(); UniqueRenderbuffer createRenderbuffer(RenderbufferType, Size size); std::unique_ptr readFramebuffer(Size, TextureFormat, bool flip); diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index da08195e58..376a784a0c 100644 --- a/src/mbgl/gl/types.hpp +++ b/src/mbgl/gl/types.hpp @@ -64,6 +64,15 @@ enum class TextureFormat : uint32_t { #endif // MBGL_USE_GLES2 }; +enum class TextureType : uint32_t { + UnsignedByte = 0x1401, +#if MBGL_USE_GLES2 + HalfFloat = 0x8D61, +#else + HalfFloat = 0x140B, +#endif // MBGL_USE_GLES2 +}; + enum class PrimitiveType { Points = 0x0000, Lines = 0x0001, diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index 5d7a6474cf..c677c84d5d 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -30,7 +30,7 @@ MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos); MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_normal_ed); MBGL_DEFINE_ATTRIBUTE(uint8_t, 1, a_fade_opacity); -MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); +MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); template struct a_data { @@ -142,6 +142,11 @@ struct a_halo_blur { using Type = gl::Attribute; }; +struct a_weight { + static auto name() { return "a_weight"; } + using Type = gl::Attribute; +}; + } // namespace attributes struct PositionOnlyLayoutAttributes : gl::Attributes< diff --git a/src/mbgl/programs/heatmap_program.cpp b/src/mbgl/programs/heatmap_program.cpp new file mode 100644 index 0000000000..67f84fbd52 --- /dev/null +++ b/src/mbgl/programs/heatmap_program.cpp @@ -0,0 +1,7 @@ +#include + +namespace mbgl { + +static_assert(sizeof(HeatmapLayoutVertex) == 4, "expected HeatmapLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_program.hpp b/src/mbgl/programs/heatmap_program.hpp new file mode 100644 index 0000000000..2d9b80404f --- /dev/null +++ b/src/mbgl/programs/heatmap_program.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(float, u_intensity); +} // namespace uniforms + +class HeatmapProgram : public Program< + shaders::heatmap, + gl::Triangle, + gl::Attributes< + attributes::a_pos>, + gl::Uniforms< + uniforms::u_intensity, + uniforms::u_matrix, + uniforms::heatmap::u_extrude_scale>, + style::HeatmapPaintProperties> +{ +public: + using Program::Program; + + /* + * @param {number} x vertex position + * @param {number} y vertex position + * @param {number} ex extrude normal + * @param {number} ey extrude normal + */ + static LayoutVertex vertex(Point p, float ex, float ey) { + return LayoutVertex { + {{ + static_cast((p.x * 2) + ((ex + 1) / 2)), + static_cast((p.y * 2) + ((ey + 1) / 2)) + }} + }; + } +}; + +using HeatmapLayoutVertex = HeatmapProgram::LayoutVertex; +using HeatmapAttributes = HeatmapProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_texture_program.cpp b/src/mbgl/programs/heatmap_texture_program.cpp new file mode 100644 index 0000000000..3b0e24eab8 --- /dev/null +++ b/src/mbgl/programs/heatmap_texture_program.cpp @@ -0,0 +1,7 @@ +#include + +namespace mbgl { + +static_assert(sizeof(HeatmapTextureLayoutVertex) == 4, "expected HeatmapTextureLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_texture_program.hpp b/src/mbgl/programs/heatmap_texture_program.hpp new file mode 100644 index 0000000000..7afe8060d0 --- /dev/null +++ b/src/mbgl/programs/heatmap_texture_program.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_color_ramp); +} // namespace uniforms + +class HeatmapTextureProgram : public Program< + shaders::heatmap_texture, + gl::Triangle, + gl::Attributes, + gl::Uniforms< + uniforms::u_matrix, + uniforms::u_world, + uniforms::u_image, + uniforms::u_color_ramp, + uniforms::u_opacity>, + style::Properties<>> { +public: + using Program::Program; + + static LayoutVertex layoutVertex(Point p) { + return LayoutVertex{ + {{ + p.x, + p.y + }} + }; + } +}; + +using HeatmapTextureLayoutVertex = HeatmapTextureProgram::LayoutVertex; +using HeatmapTextureAttributes = HeatmapTextureProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/programs.hpp b/src/mbgl/programs/programs.hpp index f533a6f633..b703323d9c 100644 --- a/src/mbgl/programs/programs.hpp +++ b/src/mbgl/programs/programs.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,6 +32,8 @@ public: fillPattern(context, programParameters), fillOutline(context, programParameters), fillOutlinePattern(context, programParameters), + heatmap(context, programParameters), + heatmapTexture(context, programParameters), hillshade(context, programParameters), hillshadePrepare(context, programParameters), line(context, programParameters), @@ -55,6 +59,8 @@ public: ProgramMap fillPattern; ProgramMap fillOutline; ProgramMap fillOutlinePattern; + ProgramMap heatmap; + HeatmapTextureProgram heatmapTexture; HillshadeProgram hillshade; HillshadePrepareProgram hillshadePrepare; ProgramMap line; diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp index 107c084918..071a27c808 100644 --- a/src/mbgl/programs/uniforms.hpp +++ b/src/mbgl/programs/uniforms.hpp @@ -37,9 +37,14 @@ MBGL_DEFINE_UNIFORM_SCALAR(Size, u_texsize); MBGL_DEFINE_UNIFORM_SCALAR(bool, u_pitch_with_map); MBGL_DEFINE_UNIFORM_SCALAR(float, u_camera_to_center_distance); MBGL_DEFINE_UNIFORM_SCALAR(float, u_fade_change); +MBGL_DEFINE_UNIFORM_SCALAR(float, u_weight); MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_extrude_scale); +namespace heatmap { +MBGL_DEFINE_UNIFORM_SCALAR(float, u_extrude_scale); +} // namespace heatmap + MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_a); MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_br_a); MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_b); diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.cpp b/src/mbgl/renderer/buckets/heatmap_bucket.cpp new file mode 100644 index 0000000000..a185e04ad2 --- /dev/null +++ b/src/mbgl/renderer/buckets/heatmap_bucket.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +using namespace style; + +HeatmapBucket::HeatmapBucket(const BucketParameters& parameters, const std::vector& layers) + : mode(parameters.mode) { + for (const auto& layer : layers) { + paintPropertyBinders.emplace( + std::piecewise_construct, + std::forward_as_tuple(layer->getID()), + std::forward_as_tuple( + layer->as()->evaluated, + parameters.tileID.overscaledZ)); + } +} + +void HeatmapBucket::upload(gl::Context& context) { + vertexBuffer = context.createVertexBuffer(std::move(vertices)); + indexBuffer = context.createIndexBuffer(std::move(triangles)); + + for (auto& pair : paintPropertyBinders) { + pair.second.upload(context); + } + + uploaded = true; +} + +bool HeatmapBucket::hasData() const { + return !segments.empty(); +} + +void HeatmapBucket::addFeature(const GeometryTileFeature& feature, + const GeometryCollection& geometry) { + constexpr const uint16_t vertexLength = 4; + + for (auto& points : geometry) { + for(auto& point : points) { + auto x = point.x; + auto y = point.y; + + // Do not include points that are outside the tile boundaries. + // Include all points in Still mode. You need to include points from + // neighbouring tiles so that they are not clipped at tile boundaries. + if ((mode == MapMode::Continuous) && + (x < 0 || x >= util::EXTENT || y < 0 || y >= util::EXTENT)) continue; + + if (segments.empty() || segments.back().vertexLength + vertexLength > std::numeric_limits::max()) { + // Move to a new segments because the old one can't hold the geometry. + segments.emplace_back(vertices.vertexSize(), triangles.indexSize()); + } + + // this geometry will be of the Point type, and we'll derive + // two triangles from it. + // + // ┌─────────┐ + // │ 4 3 │ + // │ │ + // │ 1 2 │ + // └─────────┘ + // + vertices.emplace_back(HeatmapProgram::vertex(point, -1, -1)); // 1 + vertices.emplace_back(HeatmapProgram::vertex(point, 1, -1)); // 2 + vertices.emplace_back(HeatmapProgram::vertex(point, 1, 1)); // 3 + vertices.emplace_back(HeatmapProgram::vertex(point, -1, 1)); // 4 + + auto& segment = segments.back(); + assert(segment.vertexLength <= std::numeric_limits::max()); + uint16_t index = segment.vertexLength; + + // 1, 2, 3 + // 1, 4, 3 + triangles.emplace_back(index, index + 1, index + 2); + triangles.emplace_back(index, index + 3, index + 2); + + segment.vertexLength += vertexLength; + segment.indexLength += 6; + } + } + + for (auto& pair : paintPropertyBinders) { + pair.second.populateVertexVectors(feature, vertices.vertexSize()); + } +} + +float HeatmapBucket::getQueryRadius(const RenderLayer& layer) const { + (void)layer; + return 0; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.hpp b/src/mbgl/renderer/buckets/heatmap_bucket.hpp new file mode 100644 index 0000000000..3b9f1edb81 --- /dev/null +++ b/src/mbgl/renderer/buckets/heatmap_bucket.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +class BucketParameters; + +class HeatmapBucket : public Bucket { +public: + HeatmapBucket(const BucketParameters&, const std::vector&); + + void addFeature(const GeometryTileFeature&, + const GeometryCollection&) override; + bool hasData() const override; + + void upload(gl::Context&) override; + + float getQueryRadius(const RenderLayer&) const override; + + gl::VertexVector vertices; + gl::IndexVector triangles; + SegmentVector segments; + + optional> vertexBuffer; + optional> indexBuffer; + + std::map paintPropertyBinders; + + const MapMode mode; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp new file mode 100644 index 0000000000..0f9e3239ef --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +using namespace style; + +RenderHeatmapLayer::RenderHeatmapLayer(Immutable _impl) + : RenderLayer(style::LayerType::Heatmap, _impl), + unevaluated(impl().paint.untransitioned()), colorRamp({256, 1}) { +} + +const style::HeatmapLayer::Impl& RenderHeatmapLayer::impl() const { + return static_cast(*baseImpl); +} + +std::unique_ptr RenderHeatmapLayer::createBucket(const BucketParameters& parameters, const std::vector& layers) const { + return std::make_unique(parameters, layers); +} + +void RenderHeatmapLayer::transition(const TransitionParameters& parameters) { + unevaluated = impl().paint.transitioned(parameters, std::move(unevaluated)); +} + +void RenderHeatmapLayer::evaluate(const PropertyEvaluationParameters& parameters) { + evaluated = unevaluated.evaluate(parameters); + + passes = (evaluated.get() > 0) + ? (RenderPass::Translucent | RenderPass::Pass3D) + : RenderPass::None; +} + +bool RenderHeatmapLayer::hasTransition() const { + return unevaluated.hasTransition(); +} + +void RenderHeatmapLayer::render(PaintParameters& parameters, RenderSource*) { + if (parameters.pass == RenderPass::Opaque) { + return; + } + + if (parameters.pass == RenderPass::Pass3D) { + const auto& viewportSize = parameters.staticData.backendSize; + const auto size = Size{viewportSize.width / 4, viewportSize.height / 4}; + + if (!renderTexture || renderTexture->getSize() != size) { + if (parameters.context.supportsHalfFloatTextures) { + renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::HalfFloat); + + try { + renderTexture->bind(); + } catch (const std::runtime_error& ex) { + // can't render to a half-float texture; falling back to unsigned byte one + renderTexture = nullopt; + parameters.context.supportsHalfFloatTextures = false; + } + } + + if (!renderTexture) { + renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::UnsignedByte); + renderTexture->bind(); + } + + } else { + renderTexture->bind(); + } + + if (!colorRampTexture) { + colorRampTexture = parameters.context.createTexture(colorRamp, 1, gl::TextureType::UnsignedByte); + } + + parameters.context.clear(Color{ 0.0f, 0.0f, 0.0f, 1.0f }, {}, {}); + + for (const RenderTile& tile : renderTiles) { + assert(dynamic_cast(tile.tile.getBucket(*baseImpl))); + HeatmapBucket& bucket = *reinterpret_cast(tile.tile.getBucket(*baseImpl)); + + const auto extrudeScale = tile.id.pixelsToTileUnits(1, parameters.state.getZoom()); + + const auto stencilMode = parameters.mapMode != MapMode::Continuous + ? parameters.stencilModeForClipping(tile.clip) + : gl::StencilMode::disabled(); + + parameters.programs.heatmap.get(evaluated).draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + stencilMode, + gl::ColorMode::additive(), + HeatmapProgram::UniformValues { + uniforms::u_intensity::Value{evaluated.get()}, + uniforms::u_matrix::Value{tile.matrix}, + uniforms::heatmap::u_extrude_scale::Value{extrudeScale} + }, + *bucket.vertexBuffer, + *bucket.indexBuffer, + bucket.segments, + bucket.paintPropertyBinders.at(getID()), + evaluated, + parameters.state.getZoom(), + getID() + ); + } + + } else if (parameters.pass == RenderPass::Translucent) { + parameters.context.bindTexture(renderTexture->getTexture(), 0, gl::TextureFilter::Linear); + parameters.context.bindTexture(*colorRampTexture, 1, gl::TextureFilter::Linear); + + const auto& size = parameters.staticData.backendSize; + + mat4 viewportMat; + matrix::ortho(viewportMat, 0, size.width, size.height, 0, 0, 1); + + const Properties<>::PossiblyEvaluated properties; + + parameters.programs.heatmapTexture.draw( + parameters.context, gl::Triangles(), gl::DepthMode::disabled(), + gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), + HeatmapTextureProgram::UniformValues{ + uniforms::u_matrix::Value{ viewportMat }, uniforms::u_world::Value{ size }, + uniforms::u_image::Value{ 0 }, + uniforms::u_color_ramp::Value{ 1 }, + uniforms::u_opacity::Value{ evaluated.get() } }, + parameters.staticData.extrusionTextureVertexBuffer, + parameters.staticData.quadTriangleIndexBuffer, + parameters.staticData.extrusionTextureSegments, + HeatmapTextureProgram::PaintPropertyBinders{ properties, 0 }, properties, + parameters.state.getZoom(), getID()); + } +} + +void RenderHeatmapLayer::updateColorRamp() { + auto colorValue = unevaluated.get().getValue(); + if (colorValue.isUndefined()) { + colorValue = HeatmapLayer::getDefaultHeatmapColor(); + } + + const auto length = colorRamp.bytes(); + + for (uint32_t i = 0; i < length; i += 4) { + const auto color = colorValue.evaluate(static_cast(i) / length); + colorRamp.data[i + 0] = std::floor(color.r * 255); + colorRamp.data[i + 1] = std::floor(color.g * 255); + colorRamp.data[i + 2] = std::floor(color.b * 255); + colorRamp.data[i + 3] = std::floor(color.a * 255); + } + + if (colorRampTexture) { + colorRampTexture = nullopt; + } +} + +bool RenderHeatmapLayer::queryIntersectsFeature( + const GeometryCoordinates& queryGeometry, + const GeometryTileFeature& feature, + const float zoom, + const float bearing, + const float pixelsToTileUnits) const { + (void) queryGeometry; + (void) feature; + (void) zoom; + (void) bearing; + (void) pixelsToTileUnits; + return false; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.hpp b/src/mbgl/renderer/layers/render_heatmap_layer.hpp new file mode 100644 index 0000000000..3f0b1f91b4 --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mbgl { + +class RenderHeatmapLayer: public RenderLayer { +public: + RenderHeatmapLayer(Immutable); + ~RenderHeatmapLayer() final = default; + + void transition(const TransitionParameters&) override; + void evaluate(const PropertyEvaluationParameters&) override; + bool hasTransition() const override; + void render(PaintParameters&, RenderSource*) override; + + bool queryIntersectsFeature( + const GeometryCoordinates&, + const GeometryTileFeature&, + const float, + const float, + const float) const override; + + void updateColorRamp(); + + std::unique_ptr createBucket(const BucketParameters&, const std::vector&) const override; + + // Paint properties + style::HeatmapPaintProperties::Unevaluated unevaluated; + style::HeatmapPaintProperties::PossiblyEvaluated evaluated; + + const style::HeatmapLayer::Impl& impl() const; + + PremultipliedImage colorRamp; + optional renderTexture; + optional colorRampTexture; +}; + +template <> +inline bool RenderLayer::is() const { + return type == style::LayerType::Heatmap; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp index 248905f834..bcdc175f14 100644 --- a/src/mbgl/renderer/render_layer.cpp +++ b/src/mbgl/renderer/render_layer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,8 @@ std::unique_ptr RenderLayer::create(Immutable impl) { return std::make_unique(staticImmutableCast(impl)); case LayerType::FillExtrusion: return std::make_unique(staticImmutableCast(impl)); + case LayerType::Heatmap: + return std::make_unique(staticImmutableCast(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 26a07bb093..2ac714e122 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +186,10 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (layerAdded || layerChanged) { layer.transition(transitionParameters); + + if (layer.is()) { + layer.as()->updateColorRamp(); + } } if (layerAdded || layerChanged || zoomChanged || layer.hasTransition()) { @@ -290,7 +295,11 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { RenderLayer* layer = getRenderLayer(layerImpl->id); assert(layer); - if (!parameters.staticData.has3D && (layer->is() || layer->is())) { + if (!parameters.staticData.has3D && ( + layer->is() || + layer->is() || + layer->is())) { + parameters.staticData.has3D = true; } diff --git a/src/mbgl/shaders/heatmap.cpp b/src/mbgl/shaders/heatmap.cpp new file mode 100644 index 0000000000..19927cfcc6 --- /dev/null +++ b/src/mbgl/shaders/heatmap.cpp @@ -0,0 +1,128 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include + +namespace mbgl { +namespace shaders { + +const char* heatmap::name = "heatmap"; +const char* heatmap::vertexSource = R"MBGL_SHADER( + +#ifndef HAS_UNIFORM_u_weight +uniform lowp float a_weight_t; +attribute highp vec2 a_weight; +varying highp float weight; +#else +uniform highp float u_weight; +#endif + + +#ifndef HAS_UNIFORM_u_radius +uniform lowp float a_radius_t; +attribute mediump vec2 a_radius; +#else +uniform mediump float u_radius; +#endif + + +uniform mat4 u_matrix; +uniform float u_extrude_scale; +uniform float u_opacity; +uniform float u_intensity; + +attribute vec2 a_pos; + +varying vec2 v_extrude; + +// Effective "0" in the kernel density texture to adjust the kernel size to; +// this empirically chosen number minimizes artifacts on overlapping kernels +// for typical heatmap cases (assuming clustered source) +const highp float ZERO = 1.0 / 255.0 / 16.0; + +// Gaussian kernel coefficient: 1 / sqrt(2 * PI) +#define GAUSS_COEF 0.3989422804014327 + +void main(void) { + +#ifndef HAS_UNIFORM_u_weight + weight = unpack_mix_vec2(a_weight, a_weight_t); +#else + highp float weight = u_weight; +#endif + + +#ifndef HAS_UNIFORM_u_radius + mediump float radius = unpack_mix_vec2(a_radius, a_radius_t); +#else + mediump float radius = u_radius; +#endif + + + // unencode the extrusion vector that we snuck into the a_pos vector + vec2 unscaled_extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0); + + // This 'extrude' comes in ranging from [-1, -1], to [1, 1]. We'll use + // it to produce the vertices of a square mesh framing the point feature + // we're adding to the kernel density texture. We'll also pass it as + // a varying, so that the fragment shader can determine the distance of + // each fragment from the point feature. + // Before we do so, we need to scale it up sufficiently so that the + // kernel falls effectively to zero at the edge of the mesh. + // That is, we want to know S such that + // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO + // Which solves to: + // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 + float S = sqrt(-2.0 * log(ZERO / weight / u_intensity / GAUSS_COEF)) / 3.0; + + // Pass the varying in units of radius + v_extrude = S * unscaled_extrude; + + // Scale by radius and the zoom-based scale factor to produce actual + // mesh position + vec2 extrude = v_extrude * radius * u_extrude_scale; + + // multiply a_pos by 0.5, since we had it * 2 in order to sneak + // in extrusion data + vec4 pos = vec4(floor(a_pos * 0.5) + extrude, 0, 1); + + gl_Position = u_matrix * pos; +} + +)MBGL_SHADER"; +const char* heatmap::fragmentSource = R"MBGL_SHADER( + +#ifndef HAS_UNIFORM_u_weight +varying highp float weight; +#else +uniform highp float u_weight; +#endif + + +uniform highp float u_intensity; +varying vec2 v_extrude; + +// Gaussian kernel coefficient: 1 / sqrt(2 * PI) +#define GAUSS_COEF 0.3989422804014327 + +void main() { + +#ifdef HAS_UNIFORM_u_weight + highp float weight = u_weight; +#endif + + + // Kernel density estimation with a Gaussian kernel of size 5x5 + float d = -0.5 * 3.0 * 3.0 * dot(v_extrude, v_extrude); + float val = weight * u_intensity * GAUSS_COEF * exp(d); + + gl_FragColor = vec4(val, 1.0, 1.0, 1.0); + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(1.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap.hpp b/src/mbgl/shaders/heatmap.hpp new file mode 100644 index 0000000000..a3c64db942 --- /dev/null +++ b/src/mbgl/shaders/heatmap.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class heatmap { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap_texture.cpp b/src/mbgl/shaders/heatmap_texture.cpp new file mode 100644 index 0000000000..c5d35c48ae --- /dev/null +++ b/src/mbgl/shaders/heatmap_texture.cpp @@ -0,0 +1,42 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include + +namespace mbgl { +namespace shaders { + +const char* heatmap_texture::name = "heatmap_texture"; +const char* heatmap_texture::vertexSource = R"MBGL_SHADER( +uniform mat4 u_matrix; +uniform vec2 u_world; +attribute vec2 a_pos; +varying vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos * u_world, 0, 1); + + v_pos.x = a_pos.x; + v_pos.y = 1.0 - a_pos.y; +} + +)MBGL_SHADER"; +const char* heatmap_texture::fragmentSource = R"MBGL_SHADER( +uniform sampler2D u_image; +uniform sampler2D u_color_ramp; +uniform float u_opacity; +varying vec2 v_pos; + +void main() { + float t = texture2D(u_image, v_pos).r; + vec4 color = texture2D(u_color_ramp, vec2(t, 0.5)); + gl_FragColor = color * u_opacity; + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(0.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap_texture.hpp b/src/mbgl/shaders/heatmap_texture.hpp new file mode 100644 index 0000000000..c51dc6b178 --- /dev/null +++ b/src/mbgl/shaders/heatmap_texture.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class heatmap_texture { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp index ad6998341d..19472bc8d6 100644 --- a/src/mbgl/style/conversion/layer.cpp +++ b/src/mbgl/style/conversion/layer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -165,6 +166,8 @@ optional> Converter>::operator()(c converted = convertVectorLayer(*id, value, error); } else if (*type == "raster") { converted = convertRasterLayer(*id, value, error); + } else if (*type == "heatmap") { + converted = convertVectorLayer(*id, value, error); } else if (*type == "hillshade") { converted = convertHillshadeLayer(*id, value, error); } else if (*type == "background") { diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp index adfcc4dd61..25c8fdb1ca 100644 --- a/src/mbgl/style/conversion/make_property_setters.hpp +++ b/src/mbgl/style/conversion/make_property_setters.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,7 @@ inline auto makeLayoutPropertySetters() { + return result; } @@ -166,6 +168,17 @@ inline auto makePaintPropertySetters() { result["circle-stroke-opacity"] = &setProperty, &CircleLayer::setCircleStrokeOpacity>; result["circle-stroke-opacity-transition"] = &setTransition; + result["heatmap-radius"] = &setProperty, &HeatmapLayer::setHeatmapRadius>; + result["heatmap-radius-transition"] = &setTransition; + result["heatmap-weight"] = &setProperty, &HeatmapLayer::setHeatmapWeight>; + result["heatmap-weight-transition"] = &setTransition; + result["heatmap-intensity"] = &setProperty, &HeatmapLayer::setHeatmapIntensity>; + result["heatmap-intensity-transition"] = &setTransition; + result["heatmap-color"] = &setProperty; + result["heatmap-color-transition"] = &setTransition; + result["heatmap-opacity"] = &setProperty, &HeatmapLayer::setHeatmapOpacity>; + result["heatmap-opacity-transition"] = &setTransition; + result["fill-extrusion-opacity"] = &setProperty, &FillExtrusionLayer::setFillExtrusionOpacity>; result["fill-extrusion-opacity-transition"] = &setTransition; result["fill-extrusion-color"] = &setProperty, &FillExtrusionLayer::setFillExtrusionColor>; diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp index 9e382b9c38..e3716a18dc 100644 --- a/src/mbgl/style/conversion/property_setter.hpp +++ b/src/mbgl/style/conversion/property_setter.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/src/mbgl/style/layers/heatmap_layer.cpp b/src/mbgl/style/layers/heatmap_layer.cpp new file mode 100644 index 0000000000..4989ff15f1 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer.cpp @@ -0,0 +1,239 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include +#include +#include +// for constructing default heatmap-color ramp expression from style JSON +#include +#include +#include + +namespace mbgl { +namespace style { + +HeatmapLayer::HeatmapLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable(LayerType::Heatmap, layerID, sourceID)) { +} + +HeatmapLayer::HeatmapLayer(Immutable impl_) + : Layer(std::move(impl_)) { +} + +HeatmapLayer::~HeatmapLayer() = default; + +const HeatmapLayer::Impl& HeatmapLayer::impl() const { + return static_cast(*baseImpl); +} + +Mutable HeatmapLayer::mutableImpl() const { + return makeMutable(impl()); +} + +std::unique_ptr HeatmapLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = HeatmapPaintProperties::Transitionable(); + return std::make_unique(std::move(impl_)); +} + +void HeatmapLayer::Impl::stringifyLayout(rapidjson::Writer&) const { +} + +// Source + +const std::string& HeatmapLayer::getSourceID() const { + return impl().source; +} + +void HeatmapLayer::setSourceLayer(const std::string& sourceLayer) { + auto impl_ = mutableImpl(); + impl_->sourceLayer = sourceLayer; + baseImpl = std::move(impl_); +} + +const std::string& HeatmapLayer::getSourceLayer() const { + return impl().sourceLayer; +} + +// Filter + +void HeatmapLayer::setFilter(const Filter& filter) { + auto impl_ = mutableImpl(); + impl_->filter = filter; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +const Filter& HeatmapLayer::getFilter() const { + return impl().filter; +} + +// Visibility + +void HeatmapLayer::setVisibility(VisibilityType value) { + if (value == getVisibility()) + return; + auto impl_ = mutableImpl(); + impl_->visibility = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Zoom range + +void HeatmapLayer::setMinZoom(float minZoom) { + auto impl_ = mutableImpl(); + impl_->minZoom = minZoom; + baseImpl = std::move(impl_); +} + +void HeatmapLayer::setMaxZoom(float maxZoom) { + auto impl_ = mutableImpl(); + impl_->maxZoom = maxZoom; + baseImpl = std::move(impl_); +} + +// Layout properties + + +// Paint properties + +DataDrivenPropertyValue HeatmapLayer::getDefaultHeatmapRadius() { + return { 30 }; +} + +DataDrivenPropertyValue HeatmapLayer::getHeatmapRadius() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapRadius(DataDrivenPropertyValue value) { + if (value == getHeatmapRadius()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapRadiusTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapRadiusTransition() const { + return impl().paint.template get().options; +} + +DataDrivenPropertyValue HeatmapLayer::getDefaultHeatmapWeight() { + return { 1 }; +} + +DataDrivenPropertyValue HeatmapLayer::getHeatmapWeight() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapWeight(DataDrivenPropertyValue value) { + if (value == getHeatmapWeight()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapWeightTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapWeightTransition() const { + return impl().paint.template get().options; +} + +PropertyValue HeatmapLayer::getDefaultHeatmapIntensity() { + return { 1 }; +} + +PropertyValue HeatmapLayer::getHeatmapIntensity() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapIntensity(PropertyValue value) { + if (value == getHeatmapIntensity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapIntensityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapIntensityTransition() const { + return impl().paint.template get().options; +} + +HeatmapColorPropertyValue HeatmapLayer::getDefaultHeatmapColor() { + conversion::Error error; + std::string rawValue = R"JSON(["interpolate",["linear"],["heatmap-density"],0,"rgba(0, 0, 255, 0)",0.1,"royalblue",0.3,"cyan",0.5,"lime",0.7,"yellow",1,"red"])JSON"; + return *conversion::convertJSON(rawValue, error); +} + +HeatmapColorPropertyValue HeatmapLayer::getHeatmapColor() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapColor(HeatmapColorPropertyValue value) { + if (value == getHeatmapColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapColorTransition() const { + return impl().paint.template get().options; +} + +PropertyValue HeatmapLayer::getDefaultHeatmapOpacity() { + return { 1 }; +} + +PropertyValue HeatmapLayer::getHeatmapOpacity() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapOpacity(PropertyValue value) { + if (value == getHeatmapOpacity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapOpacityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapOpacityTransition() const { + return impl().paint.template get().options; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.cpp b/src/mbgl/style/layers/heatmap_layer_impl.cpp new file mode 100644 index 0000000000..af20888d9d --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.cpp @@ -0,0 +1,15 @@ +#include + +namespace mbgl { +namespace style { + +bool HeatmapLayer::Impl::hasLayoutDifference(const Layer::Impl& other) const { + assert(dynamic_cast(&other)); + const auto& impl = static_cast(other); + return filter != impl.filter || + visibility != impl.visibility || + paint.hasDataDrivenPropertyDifference(impl.paint); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.hpp b/src/mbgl/style/layers/heatmap_layer_impl.hpp new file mode 100644 index 0000000000..cc27c3076a --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace style { + +class HeatmapLayer::Impl : public Layer::Impl { +public: + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer&) const override; + + HeatmapPaintProperties::Transitionable paint; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.cpp b/src/mbgl/style/layers/heatmap_layer_properties.cpp new file mode 100644 index 0000000000..2edb839589 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.cpp @@ -0,0 +1,9 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include + +namespace mbgl { +namespace style { + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.hpp b/src/mbgl/style/layers/heatmap_layer_properties.hpp new file mode 100644 index 0000000000..f7afa5fbeb --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.hpp @@ -0,0 +1,40 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +struct HeatmapRadius : DataDrivenPaintProperty { + static float defaultValue() { return 30; } +}; + +struct HeatmapWeight : DataDrivenPaintProperty { + static float defaultValue() { return 1; } +}; + +struct HeatmapIntensity : PaintProperty { + static float defaultValue() { return 1; } +}; + +struct HeatmapOpacity : PaintProperty { + static float defaultValue() { return 1; } +}; + +class HeatmapPaintProperties : public Properties< + HeatmapRadius, + HeatmapWeight, + HeatmapIntensity, + HeatmapColor, + HeatmapOpacity +> {}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs index be44bb353d..657a7f5a8a 100644 --- a/src/mbgl/style/layers/layer.cpp.ejs +++ b/src/mbgl/style/layers/layer.cpp.ejs @@ -8,6 +8,12 @@ #include _layer.hpp> #include _layer_impl.hpp> #include +<% if (type === 'heatmap') { -%> +// for constructing default heatmap-color ramp expression from style JSON +#include +#include +#include +<% } -%> namespace mbgl { namespace style { @@ -134,7 +140,13 @@ void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>(<%- propertyV // Paint properties <% for (const property of paintProperties) { %> <%- propertyValueType(property) %> <%- camelize(type) %>Layer::getDefault<%- camelize(property.name) %>() { +<% if (property.name === 'heatmap-color') { -%> + conversion::Error error; + std::string rawValue = R"JSON(<%- JSON.stringify(property.default) %>)JSON"; + return *conversion::convertJSON<<%- propertyValueType(property)%>>(rawValue, error); +<% } else { -%> return { <%- defaultValue(property) %> }; +<% } -%> } <%- propertyValueType(property) %> <%- camelize(type) %>Layer::get<%- camelize(property.name) %>() const { diff --git a/src/mbgl/style/layers/layer_properties.hpp.ejs b/src/mbgl/style/layers/layer_properties.hpp.ejs index cde1b80b7b..1bceb84960 100644 --- a/src/mbgl/style/layers/layer_properties.hpp.ejs +++ b/src/mbgl/style/layers/layer_properties.hpp.ejs @@ -25,6 +25,7 @@ struct <%- camelize(property.name) %> : <%- layoutPropertyType(property, type) % <% } -%> <% for (const property of paintProperties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> struct <%- camelize(property.name) %> : <%- paintPropertyType(property, type) %> { static <%- evaluatedType(property) %> defaultValue() { return <%- defaultValue(property) %>; } }; diff --git a/src/mbgl/style/paint_property.hpp b/src/mbgl/style/paint_property.hpp index c4c996b3bd..195eb645a9 100644 --- a/src/mbgl/style/paint_property.hpp +++ b/src/mbgl/style/paint_property.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -48,5 +49,27 @@ public: static constexpr bool IsDataDriven = false; }; +/* + * Special-case paint property traits for heatmap-color, needed because + * heatmap-color values do not fit into the + * Undefined | Value | {Camera,Source,Composite}Function taxonomy that applies + * to all other paint properties. + * + * These traits are provided here--despite the fact that heatmap-color + * is not used like other paint properties--to allow the parameter-pack-based + * batch evaluation of paint properties to compile properly. + */ +class HeatmapColor { +public: + using TransitionableType = Transitionable; + using UnevaluatedType = Transitioning; + using EvaluatorType = PropertyEvaluator; + using PossiblyEvaluatedType = Color; + using Type = Color; + static constexpr bool IsDataDriven = false; + + static Color defaultValue() { return {}; } +}; + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index f2a9c0f222..d330b3120a 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mbgl/util/offscreen_texture.cpp b/src/mbgl/util/offscreen_texture.cpp index 339e74b250..03f555eae0 100644 --- a/src/mbgl/util/offscreen_texture.cpp +++ b/src/mbgl/util/offscreen_texture.cpp @@ -11,20 +11,21 @@ OffscreenTexture& OffscreenTexture::operator=(OffscreenTexture&&) = default; class OffscreenTexture::Impl { public: - Impl(gl::Context& context_, const Size size_) - : context(context_), size(std::move(size_)) { + Impl(gl::Context& context_, const Size size_, const gl::TextureType type_) + : context(context_), size(std::move(size_)), type(type_) { assert(!size.isEmpty()); } Impl(gl::Context& context_, const Size size_, - gl::Renderbuffer& depth_) - : context(context_), size(std::move(size_)), depth(&depth_) { + gl::Renderbuffer& depth_, + const gl::TextureType type_) + : context(context_), size(std::move(size_)), depth(&depth_), type(type_) { assert(!size.isEmpty()); } void bind() { if (!framebuffer) { - texture = context.createTexture(size, gl::TextureFormat::RGBA); + texture = context.createTexture(size, gl::TextureFormat::RGBA, 0, type); if (depth) { framebuffer = context.createFramebuffer(*texture, *depth); } else { @@ -58,18 +59,21 @@ private: optional framebuffer; optional texture; gl::Renderbuffer* depth = nullptr; + const gl::TextureType type; }; OffscreenTexture::OffscreenTexture(gl::Context& context, - const Size size) - : impl(std::make_unique(context, std::move(size))) { + const Size size, + const gl::TextureType type) + : impl(std::make_unique(context, std::move(size), type)) { assert(!size.isEmpty()); } OffscreenTexture::OffscreenTexture(gl::Context& context, const Size size, - gl::Renderbuffer& renderbuffer) - : impl(std::make_unique(context, std::move(size), renderbuffer)) { + gl::Renderbuffer& renderbuffer, + const gl::TextureType type) + : impl(std::make_unique(context, std::move(size), renderbuffer, type)) { assert(!size.isEmpty()); } diff --git a/src/mbgl/util/offscreen_texture.hpp b/src/mbgl/util/offscreen_texture.hpp index 7f7e0f0338..36f24f16d3 100644 --- a/src/mbgl/util/offscreen_texture.hpp +++ b/src/mbgl/util/offscreen_texture.hpp @@ -12,10 +12,12 @@ class Texture; class OffscreenTexture { public: OffscreenTexture(gl::Context&, - Size size = { 256, 256 }); + Size size = { 256, 256 }, + gl::TextureType type = gl::TextureType::UnsignedByte); OffscreenTexture(gl::Context&, Size size, - gl::Renderbuffer&); + gl::Renderbuffer&, + gl::TextureType type = gl::TextureType::UnsignedByte); ~OffscreenTexture(); OffscreenTexture(OffscreenTexture&&); OffscreenTexture& operator=(OffscreenTexture&&); -- cgit v1.2.1