diff options
author | Vladimir Agafonkin <agafonkin@gmail.com> | 2017-12-08 22:28:03 +0200 |
---|---|---|
committer | Minh Nguyễn <mxn@1ec5.org> | 2018-02-06 08:57:41 -0800 |
commit | 4b52b9d4231e887304b46a4ffe61cbf2aab8f56f (patch) | |
tree | d3d4ff5a774a0710113c5238c149b52c14efbc0b | |
parent | f7cae4c3e49282417d87a98a64bc94b12993855d (diff) | |
download | qtlocation-mapboxgl-4b52b9d4231e887304b46a4ffe61cbf2aab8f56f.tar.gz |
[core] add heatmap layer
55 files changed, 4027 insertions, 32 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 24d5262799..189f83be09 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -150,6 +150,8 @@ set(MBGL_CORE_FILES src/mbgl/programs/hillshade_prepare_program.hpp src/mbgl/programs/hillshade_program.cpp src/mbgl/programs/hillshade_program.hpp + src/mbgl/programs/heatmap_program.cpp + src/mbgl/programs/heatmap_program.hpp src/mbgl/programs/line_program.cpp src/mbgl/programs/line_program.hpp src/mbgl/programs/program.hpp @@ -227,6 +229,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp src/mbgl/renderer/buckets/hillshade_bucket.cpp src/mbgl/renderer/buckets/hillshade_bucket.hpp + src/mbgl/renderer/buckets/heatmap_bucket.cpp + src/mbgl/renderer/buckets/heatmap_bucket.hpp src/mbgl/renderer/buckets/line_bucket.cpp src/mbgl/renderer/buckets/line_bucket.hpp src/mbgl/renderer/buckets/raster_bucket.cpp @@ -247,6 +251,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/layers/render_fill_layer.hpp src/mbgl/renderer/layers/render_hillshade_layer.cpp src/mbgl/renderer/layers/render_hillshade_layer.hpp + src/mbgl/renderer/layers/render_heatmap_layer.cpp + src/mbgl/renderer/layers/render_heatmap_layer.hpp src/mbgl/renderer/layers/render_line_layer.cpp src/mbgl/renderer/layers/render_line_layer.hpp src/mbgl/renderer/layers/render_raster_layer.cpp @@ -301,6 +307,10 @@ set(MBGL_CORE_FILES src/mbgl/shaders/hillshade.hpp src/mbgl/shaders/hillshade_prepare.cpp src/mbgl/shaders/hillshade_prepare.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/line.cpp src/mbgl/shaders/line.hpp src/mbgl/shaders/line_pattern.cpp @@ -349,6 +359,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 +412,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 @@ -496,6 +508,7 @@ set(MBGL_CORE_FILES include/mbgl/style/layers/fill_extrusion_layer.hpp include/mbgl/style/layers/fill_layer.hpp include/mbgl/style/layers/hillshade_layer.hpp + include/mbgl/style/layers/heatmap_layer.hpp include/mbgl/style/layers/line_layer.hpp include/mbgl/style/layers/raster_layer.hpp include/mbgl/style/layers/symbol_layer.hpp @@ -527,6 +540,11 @@ set(MBGL_CORE_FILES src/mbgl/style/layers/hillshade_layer_impl.hpp src/mbgl/style/layers/hillshade_layer_properties.cpp src/mbgl/style/layers/hillshade_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/line_layer.cpp src/mbgl/style/layers/line_layer_impl.cpp src/mbgl/style/layers/line_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..72b17bc112 --- /dev/null +++ b/include/mbgl/style/conversion/heatmap_color_property_value.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include <mbgl/style/heatmap_color_property_value.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/constant.hpp> +#include <mbgl/style/conversion/function.hpp> +#include <mbgl/style/conversion/expression.hpp> +#include <mbgl/style/expression/value.hpp> +#include <mbgl/style/expression/is_constant.hpp> +#include <mbgl/style/expression/is_expression.hpp> +#include <mbgl/style/expression/find_zoom_curve.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter<HeatmapColorPropertyValue> { + optional<HeatmapColorPropertyValue> operator()(const Convertible& value, Error& error) const { + if (isUndefined(value)) { + return HeatmapColorPropertyValue(); + } else if (isExpression(value)) { + optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(value, error, expression::type::Color); + if (!expression) { + return {}; + } + 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..7d97b5e137 --- /dev/null +++ b/include/mbgl/style/heatmap_color_property_value.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include <mbgl/util/variant.hpp> +#include <mbgl/style/undefined.hpp> +#include <mbgl/style/function/camera_function.hpp> + +namespace mbgl { +namespace style { + +/* + * Special-case implementation of (a subset of) the PropertyValue<T> interface + * used for building the HeatmapColor paint property traits class. + */ +class HeatmapColorPropertyValue { +private: + std::shared_ptr<expression::Expression> value; + + friend bool operator==(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return *(lhs.value) == *(rhs.value); + } + + friend bool operator!=(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return !(lhs == rhs); + } + +public: + HeatmapColorPropertyValue() : value(nullptr) {} + HeatmapColorPropertyValue(std::shared_ptr<expression::Expression> value_) : value(std::move(value_)) {} + + bool isUndefined() const { return value.get() == nullptr; } + + // noop, needed for batch evaluation of paint property values to compile + template <typename Evaluator> + Color evaluate(const Evaluator&, TimePoint = {}) const { return {}; } + + Color evaluate(double heatmapDensity) const { + const auto result = value->evaluate(expression::EvaluationContext({}, nullptr, {heatmapDensity})); + return *expression::fromExpressionValue<Color>(*result); + } + + bool isDataDriven() const { return false; } + bool hasDataDrivenPropertyDifference(const HeatmapColorPropertyValue&) const { return false; } +}; + + +} // 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<V>(visitor)(*as<CustomLayer>()); case LayerType::FillExtrusion: return std::forward<V>(visitor)(*as<FillExtrusionLayer>()); + case LayerType::Heatmap: + return std::forward<V>(visitor)(*as<HeatmapLayer>()); } diff --git a/include/mbgl/style/layer_type.hpp b/include/mbgl/style/layer_type.hpp index 951757134b..69390d642a 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 <mbgl/style/layer.hpp> +#include <mbgl/style/filter.hpp> +#include <mbgl/style/property_value.hpp> +#include <mbgl/style/data_driven_property_value.hpp> +#include <mbgl/style/heatmap_color_property_value.hpp> + +#include <mbgl/util/color.hpp> + +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<float> getDefaultHeatmapRadius(); + DataDrivenPropertyValue<float> getHeatmapRadius() const; + void setHeatmapRadius(DataDrivenPropertyValue<float>); + void setHeatmapRadiusTransition(const TransitionOptions&); + TransitionOptions getHeatmapRadiusTransition() const; + + static DataDrivenPropertyValue<float> getDefaultHeatmapWeight(); + DataDrivenPropertyValue<float> getHeatmapWeight() const; + void setHeatmapWeight(DataDrivenPropertyValue<float>); + void setHeatmapWeightTransition(const TransitionOptions&); + TransitionOptions getHeatmapWeightTransition() const; + + static PropertyValue<float> getDefaultHeatmapIntensity(); + PropertyValue<float> getHeatmapIntensity() const; + void setHeatmapIntensity(PropertyValue<float>); + 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<float> getDefaultHeatmapOpacity(); + PropertyValue<float> getHeatmapOpacity() const; + void setHeatmapOpacity(PropertyValue<float>); + void setHeatmapOpacityTransition(const TransitionOptions&); + TransitionOptions getHeatmapOpacityTransition() const; + + // Private implementation + + class Impl; + const Impl& impl() const; + + Mutable<Impl> mutableImpl() const; + HeatmapLayer(Immutable<Impl>); + std::unique_ptr<Layer> cloneRef(const std::string& id) const final; +}; + +template <> +inline bool Layer::is<HeatmapLayer>() 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 <mbgl/style/filter.hpp> #include <mbgl/style/property_value.hpp> #include <mbgl/style/data_driven_property_value.hpp> +<% if (type === 'heatmap') { -%> +#include <mbgl/style/heatmap_color_property_value.hpp> +<% } -%> #include <mbgl/util/color.hpp> 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..bfc663449f --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java @@ -0,0 +1,249 @@ +// 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 <a href="https://www.mapbox.com/mapbox-gl-style-spec/#layers-heatmap">The online documentation</a> + */ +@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<Float> getHeatmapRadius() { + return (PropertyValue<Float>) 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<Float> getHeatmapWeight() { + return (PropertyValue<Float>) new PropertyValue("heatmap-weight", nativeGetHeatmapWeight()); + } + + /** + * Get the HeatmapIntensity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue<Float> getHeatmapIntensity() { + return (PropertyValue<Float>) 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 HeatmapColor property + * + * @return property wrapper value around String + */ + @SuppressWarnings("unchecked") + public PropertyValue<String> getHeatmapColor() { + return (PropertyValue<String>) new PropertyValue("heatmap-color", nativeGetHeatmapColor()); + } + + /** + * Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `["heatmap-density"]` as input. + * + * @return int representation of a rgba string color + * @throws RuntimeException thrown if property isn't a value + */ + @ColorInt + public int getHeatmapColorAsInt() { + PropertyValue<String> value = getHeatmapColor(); + if (value.isValue()) { + return rgbaToColor(value.getValue()); + } else { + throw new RuntimeException("heatmap-color was set as a Function"); + } + } + + /** + * Get the HeatmapOpacity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue<Float> getHeatmapOpacity() { + return (PropertyValue<Float>) 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 nativeGetHeatmapColor(); + + 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..43f5be98cb 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 @@ -1516,6 +1516,168 @@ public class PropertyFactory { } /** + * 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<Float> 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<Expression> 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 <T> the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <T> PropertyValue<Function<T, Float>> heatmapRadius(Function<T, Float> 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<Float> 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<Expression> 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 <T> the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <T> PropertyValue<Function<T, Float>> heatmapWeight(Function<T, Float> 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<Float> 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<Expression> 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 <Z> the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> heatmapIntensity(CameraFunction<Z, Float> function) { + return new PaintPropertyValue<>("heatmap-intensity", function); + } + + /** + * Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `["heatmap-density"]` as input. + * + * @param value a int color value + * @return property wrapper around String color + */ + public static PropertyValue<String> heatmapColor(@ColorInt int value) { + return new PaintPropertyValue<>("heatmap-color", colorToRgbaString(value)); + } + + /** + * Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `["heatmap-density"]` as input. + * + * @param value a String value + * @return property wrapper around String + */ + public static PropertyValue<String> heatmapColor(String value) { + return new PaintPropertyValue<>("heatmap-color", value); + } + + /** + * Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `["heatmap-density"]` as input. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue<Expression> heatmapColor(Expression expression) { + return new PaintPropertyValue<>("heatmap-color", expression); + } + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue<Float> 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<Expression> heatmapOpacity(Expression expression) { + return new PaintPropertyValue<>("heatmap-opacity", expression); + } + + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param <Z> the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> heatmapOpacity(CameraFunction<Z, Float> 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. * * @param value a Float value 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..0a4c35f1fe --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java @@ -0,0 +1,660 @@ +// 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.<Float>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<Stop.CompositeValue<Float, Float>, Float> stops = + (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getHeatmapRadius().getFunction().getStops(); + Stop<Stop.CompositeValue<Float, Float>, 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.<Float>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<Stop.CompositeValue<Float, Float>, Float> stops = + (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getHeatmapWeight().getFunction().getStops(); + Stop<Stop.CompositeValue<Float, Float>, 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 testHeatmapColorAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-color"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapColor("rgba(0, 0, 0, 1)")); + assertEquals((String) layer.getHeatmapColor().getValue(), (String) "rgba(0, 0, 0, 1)"); + } + }); + } + + @Test + public void testHeatmapColorAsIntConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-color"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapColor(Color.RED)); + assertEquals(layer.getHeatmapColorAsInt(), Color.RED); + } + }); + } + + @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/src/style/layers/heatmap_layer.cpp b/platform/android/src/style/layers/heatmap_layer.cpp new file mode 100644 index 0000000000..3f1d35feb2 --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.cpp @@ -0,0 +1,141 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +#include "heatmap_layer.hpp" + +#include <string> + +#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<mbgl::style::HeatmapLayer>(jni::Make<std::string>(env, layerId), jni::Make<std::string>(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<mbgl::style::HeatmapLayer> coreLayer) + : Layer(map, std::move(coreLayer)) { + } + + HeatmapLayer::~HeatmapLayer() = default; + + // Property getters + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapRadius(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapRadius()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<TransitionOptions> HeatmapLayer::getHeatmapRadiusTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapRadiusTransition(); + return *convert<jni::Object<TransitionOptions>>(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<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapRadiusTransition(options); + } + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapWeight(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapWeight()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapIntensity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapIntensity()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<TransitionOptions> HeatmapLayer::getHeatmapIntensityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapIntensityTransition(); + return *convert<jni::Object<TransitionOptions>>(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<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapIntensityTransition(options); + } + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapColor(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapColor()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapOpacity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapOpacity()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<TransitionOptions> HeatmapLayer::getHeatmapOpacityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapOpacityTransition(); + return *convert<jni::Object<TransitionOptions>>(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<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapOpacityTransition(options); + } + + + jni::Class<HeatmapLayer> HeatmapLayer::javaClass; + + jni::jobject* HeatmapLayer::createJavaPeer(jni::JNIEnv& env) { + static auto constructor = HeatmapLayer::javaClass.template GetConstructor<jni::jlong>(env); + return HeatmapLayer::javaClass.New(env, constructor, reinterpret_cast<jni::jlong>(this)); + } + + void HeatmapLayer::registerNative(jni::JNIEnv& env) { + // Lookup the class + HeatmapLayer::javaClass = *jni::Class<HeatmapLayer>::Find(env).NewGlobalRef(env).release(); + + #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name) + + // Register the peer + jni::RegisterNativePeer<HeatmapLayer>( + env, HeatmapLayer::javaClass, "nativePtr", + std::make_unique<HeatmapLayer, JNIEnv&, jni::String, jni::String>, + "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::getHeatmapColor, "nativeGetHeatmapColor"), + 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..9e8908b062 --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.hpp @@ -0,0 +1,52 @@ +// 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 <mbgl/style/layers/heatmap_layer.hpp> +#include <jni/jni.hpp> + +namespace mbgl { +namespace android { + +class HeatmapLayer : public Layer { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/layers/HeatmapLayer"; }; + + static jni::Class<HeatmapLayer> 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<mbgl::style::HeatmapLayer>); + + ~HeatmapLayer(); + + // Properties + + jni::Object<jni::ObjectTag> getHeatmapRadius(jni::JNIEnv&); + void setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object<TransitionOptions> getHeatmapRadiusTransition(jni::JNIEnv&); + + jni::Object<jni::ObjectTag> getHeatmapWeight(jni::JNIEnv&); + + jni::Object<jni::ObjectTag> getHeatmapIntensity(jni::JNIEnv&); + void setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object<TransitionOptions> getHeatmapIntensityTransition(jni::JNIEnv&); + + jni::Object<jni::ObjectTag> getHeatmapColor(jni::JNIEnv&); + + jni::Object<jni::ObjectTag> getHeatmapOpacity(jni::JNIEnv&); + void setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object<TransitionOptions> getHeatmapOpacityTransition(jni::JNIEnv&); + jni::jobject* createJavaPeer(jni::JNIEnv&); + +}; // class HeatmapLayer + +} // namespace android +} // namespace mbgl diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index a7804ac948..af39584c91 100755 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -414,6 +414,7 @@ global.describeValue = function (value, property, layerType) { } return displayValue; case 'color': + if (property.name === 'heatmap-color') value = 'red'; let color = parseColor(value); if (!color) { throw new Error(`unrecognized color format in default value of ${property.name}`); @@ -632,6 +633,7 @@ const layers = _(spec.layer.type.values).map((value, layerType) => { }, []); const paintProperties = Object.keys(spec[`paint_${layerType}`]).reduce((memo, name) => { + if (name === 'heatmap-color') return memo; spec[`paint_${layerType}`][name].name = name; memo.push(spec[`paint_${layerType}`][name]); return memo; diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.h b/platform/darwin/src/MGLHeatmapStyleLayer.h new file mode 100644 index 0000000000..aa6224a5a0 --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.h @@ -0,0 +1,144 @@ +// 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 + +/** + A heatmap. + + 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 + ``` + */ +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 + +/** + 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..b210114d90 --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.mm @@ -0,0 +1,193 @@ +// 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 <mbgl/style/transition_options.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> + +@interface MGLHeatmapStyleLayer () + +@property (nonatomic, readonly) mbgl::style::HeatmapLayer *rawLayer; + +@end + +@implementation MGLHeatmapStyleLayer + +- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source +{ + auto layer = std::make_unique<mbgl::style::HeatmapLayer>(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)setHeatmapIntensity:(NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::PropertyValue<float>>(heatmapIntensity); + self.rawLayer->setHeatmapIntensity(mbglValue); +} + +- (NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapIntensity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapIntensity(); + } + return MGLStyleValueTransformer<float, NSNumber *>().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<float, NSNumber *>().toPropertyValue<mbgl::style::PropertyValue<float>>(heatmapOpacity); + self.rawLayer->setHeatmapOpacity(mbglValue); +} + +- (NSExpression *)heatmapOpacity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapOpacity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapOpacity(); + } + return MGLStyleValueTransformer<float, NSNumber *>().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<float, NSNumber *>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<float>>(heatmapRadius); + self.rawLayer->setHeatmapRadius(mbglValue); +} + +- (NSExpression *)heatmapRadius { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapRadius(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapRadius(); + } + return MGLStyleValueTransformer<float, NSNumber *>().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<float, NSNumber *>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<float>>(heatmapWeight); + self.rawLayer->setHeatmapWeight(mbglValue); +} + +- (NSExpression *)heatmapWeight { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapWeight(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapWeight(); + } + return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue); +} + +@end diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 5128944312..5763f65c03 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -5,6 +5,7 @@ #import "MGLStyleLayer_Private.h" #import "MGLFillStyleLayer.h" #import "MGLFillExtrusionStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLLineStyleLayer.h" #import "MGLCircleStyleLayer.h" #import "MGLSymbolStyleLayer.h" @@ -34,6 +35,7 @@ #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> @@ -218,7 +220,7 @@ static NSURL *MGLStyleURL_trafficNight; - (MGLSource *)sourceWithIdentifier:(NSString *)identifier { auto rawSource = self.rawStyle->getSource(identifier.UTF8String); - + return rawSource ? [self sourceFromMBGLSource:rawSource] : nil; } @@ -395,6 +397,8 @@ static NSURL *MGLStyleURL_trafficNight; return [[MGLFillStyleLayer alloc] initWithRawLayer:fillLayer]; } else if (auto fillExtrusionLayer = rawLayer->as<mbgl::style::FillExtrusionLayer>()) { return [[MGLFillExtrusionStyleLayer alloc] initWithRawLayer:fillExtrusionLayer]; + } else if (auto heatmapLayer = rawLayer->as<mbgl::style::HeatmapLayer>()) { + return [[MGLHeatmapStyleLayer alloc] initWithRawLayer:heatmapLayer]; } else if (auto lineLayer = rawLayer->as<mbgl::style::LineLayer>()) { return [[MGLLineStyleLayer alloc] initWithRawLayer:lineLayer]; } else if (auto symbolLayer = rawLayer->as<mbgl::style::SymbolLayer>()) { @@ -606,7 +610,7 @@ static NSURL *MGLStyleURL_trafficNight; auto transitionOptions = self.rawStyle->getTransitionOptions(); transitionOptions.duration = MGLDurationFromTimeInterval(transition.duration); transitionOptions.delay = MGLDurationFromTimeInterval(transition.delay); - + self.rawStyle->setTransitionOptions(transitionOptions); } @@ -617,7 +621,7 @@ static NSURL *MGLStyleURL_trafficNight; transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); - + return transition; } @@ -653,7 +657,7 @@ static NSURL *MGLStyleURL_trafficNight; } else { return; } - + if (_localizesLabels) { NSString *preferredLanguage = [MGLVectorSource preferredMapboxStreetsLanguage]; NSMutableDictionary *localizedKeysByKeyBySourceIdentifier = [NSMutableDictionary dictionary]; @@ -661,17 +665,17 @@ static NSURL *MGLStyleURL_trafficNight; if (![layer isKindOfClass:[MGLSymbolStyleLayer class]]) { continue; } - + MGLVectorSource *source = (MGLVectorSource *)[self sourceWithIdentifier:layer.sourceIdentifier]; if (![source isKindOfClass:[MGLVectorSource class]] || !source.mapboxStreets) { continue; } - + NSDictionary *localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier]; if (!localizedKeysByKey) { localizedKeysByKey = localizedKeysByKeyBySourceIdentifier[layer.sourceIdentifier] = [source localizedKeysByKeyForPreferredLanguage:preferredLanguage]; } - + NSString *(^stringByLocalizingString)(NSString *) = ^ NSString * (NSString *string) { NSMutableString *localizedString = string.mutableCopy; [localizedKeysByKey enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull localizedKey, BOOL * _Nonnull stop) { @@ -684,7 +688,7 @@ static NSURL *MGLStyleURL_trafficNight; }]; return localizedString; }; - + if (layer.text.expressionType == NSConstantValueExpressionType) { NSString *textField = layer.text.constantValue; NSString *localizingString = stringByLocalizingString(textField); @@ -697,10 +701,10 @@ static NSURL *MGLStyleURL_trafficNight; } } } else { - + [self.localizedLayersByIdentifier enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSDictionary<NSObject *, MGLTextLanguage *> *textFields, BOOL *done) { MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:identifier]; - + if (layer.text.expressionType == NSConstantValueExpressionType) { NSString *textField = layer.text.constantValue; [textFields enumerateKeysAndObjectsUsingBlock:^(NSObject *originalLanguage, MGLTextLanguage *textLanguage, BOOL *done) { @@ -724,7 +728,7 @@ static NSURL *MGLStyleURL_trafficNight; - (NS_ARRAY_OF(MGLStyleLayer *) *)placeStyleLayers { NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"]; - + NSSet *placeSourceLayerIdentifiers = [NSSet setWithObjects:@"marine_label", @"country_label", @"state_label", @"place_label", @"water_label", @"poi_label", @"rail_station_label", @"mountain_peak_label", nil]; NSPredicate *isPlacePredicate = [NSPredicate predicateWithBlock:^BOOL (MGLVectorStyleLayer * _Nullable layer, NSDictionary<NSString *, id> * _Nullable bindings) { return [layer isKindOfClass:[MGLVectorStyleLayer class]] && [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && [placeSourceLayerIdentifiers containsObject:layer.sourceLayerIdentifier]; @@ -734,7 +738,7 @@ static NSURL *MGLStyleURL_trafficNight; - (NS_ARRAY_OF(MGLStyleLayer *) *)roadStyleLayers { NSSet *streetsSourceIdentifiers = [self.mapboxStreetsSources valueForKey:@"identifier"]; - + NSPredicate *isPlacePredicate = [NSPredicate predicateWithBlock:^BOOL (MGLVectorStyleLayer * _Nullable layer, NSDictionary<NSString *, id> * _Nullable bindings) { return [layer isKindOfClass:[MGLVectorStyleLayer class]] && [streetsSourceIdentifiers containsObject:layer.sourceIdentifier] && [layer.sourceLayerIdentifier isEqualToString:@"road_label"]; }]; 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 <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/transition_options.hpp> + +@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<mbgl::style::HeatmapLayer>()); + auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>(); + + 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<float> 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<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { 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<float> 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<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { 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<float> 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<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { 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<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "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<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "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<float> 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<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { 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<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "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<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "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/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md index 51cd87a766..ddbdd23a09 100644 --- a/platform/ios/docs/guides/For Style Authors.md +++ b/platform/ios/docs/guides/For Style Authors.md @@ -190,6 +190,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/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/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index 4a16066a2a..666748dd91 100644 --- a/platform/macos/docs/guides/For Style Authors.md +++ b/platform/macos/docs/guides/For Style Authors.md @@ -177,6 +177,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/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 090902fca4..7f51981647 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -85,6 +85,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 */; }; + 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 */; }; @@ -369,6 +372,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 = "<group>"; }; 55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = "<group>"; }; + 89462398200D199100DA8EF2 /* heatmap.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = heatmap.json; sourceTree = "<group>"; }; + 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = "<group>"; }; + 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = "<group>"; }; 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = "<group>"; }; 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = "<group>"; }; 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLSourceQueryTests.m; sourceTree = "<group>"; }; @@ -662,6 +668,8 @@ 35136D471D42295400C20EFD /* Layers */ = { isa = PBXGroup; children = ( + 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */, + 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */, DA8F25851D51C9E10010E6B5 /* MGLBackgroundStyleLayer.h */, DA8F25861D51C9E10010E6B5 /* MGLBackgroundStyleLayer.mm */, 3527428B1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.h */, @@ -825,6 +833,7 @@ DA839EA61CC2E3400062CAFB /* Info.plist */, 96E027331E57C9A7004B8E66 /* Localizable.strings */, DA839E981CC2E3400062CAFB /* Supporting Files */, + 89462398200D199100DA8EF2 /* heatmap.json */, ); name = "Demo App"; path = app; @@ -1232,6 +1241,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 +1432,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 +1514,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 */, diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 3e66aaa789..879cae8a4f 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -16,6 +16,7 @@ #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> #include <mbgl/style/layers/symbol_layer.hpp> @@ -529,7 +530,7 @@ void NodeMap::release() { uv_close(reinterpret_cast<uv_handle_t *>(async), [] (uv_handle_t *h) { delete reinterpret_cast<uv_async_t *>(h); }); - + map.reset(); frontend.reset(); } @@ -557,7 +558,7 @@ void NodeMap::Cancel(const Nan::FunctionCallbackInfo<v8::Value>& info) { void NodeMap::cancel() { auto style = map->getStyle().getJSON(); - + // Reset map explicitly as it resets the renderer frontend map.reset(); @@ -706,7 +707,7 @@ void NodeMap::AddImage(const Nan::FunctionCallbackInfo<v8::Value>& info) { float pixelRatio = Nan::Get(optionObject, Nan::New("pixelRatio").ToLocalChecked()).ToLocalChecked()->NumberValue(); auto imageBuffer = Nan::To<v8::Object>(info[1]).ToLocalChecked()->ToObject(); - + char * imageDataBuffer = node::Buffer::Data(imageBuffer); size_t imageLength = node::Buffer::Length(imageBuffer); @@ -716,7 +717,7 @@ void NodeMap::AddImage(const Nan::FunctionCallbackInfo<v8::Value>& info) { std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(imageLength); std::copy(imageDataBuffer, imageDataBuffer + imageLength, data.get()); - + mbgl::UnassociatedImage cImage({ imageWidth, imageHeight}, std::move(data)); mbgl::PremultipliedImage cPremultipliedImage = mbgl::util::premultiply(std::move(cImage)); nodeMap->map->getStyle().addImage(std::make_unique<mbgl::style::Image>(*Nan::Utf8String(info[0]), std::move(cPremultipliedImage), pixelRatio)); @@ -1045,9 +1046,9 @@ void NodeMap::QueryRenderedFeatures(const Nan::FunctionCallbackInfo<v8::Value>& if (!info[1]->IsObject()) { return Nan::ThrowTypeError("options argument must be an object"); } - + auto options = Nan::To<v8::Object>(info[1]).ToLocalChecked(); - + //Check if layers is set. If provided, it must be an array of strings if (Nan::Has(options, Nan::New("layers").ToLocalChecked()).FromJust()) { auto layersOption = Nan::Get(options, Nan::New("layers").ToLocalChecked()).ToLocalChecked(); @@ -1061,7 +1062,7 @@ void NodeMap::QueryRenderedFeatures(const Nan::FunctionCallbackInfo<v8::Value>& } queryOptions.layerIDs = layersVec; } - + //Check if filter is provided. If set it must be a valid Filter object if (Nan::Has(options, Nan::New("filter").ToLocalChecked()).FromJust()) { auto filterOption = Nan::Get(options, Nan::New("filter").ToLocalChecked()).ToLocalChecked(); 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..8a9c9d4144 100644 --- a/scripts/style-spec.js +++ b/scripts/style-spec.js @@ -1,5 +1 @@ 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.hpp b/src/mbgl/gl/context.hpp index 14f078367f..e5a57666de 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -81,7 +81,7 @@ public: createIndexBuffer(v.data(), v.byteSize(), usage) }; } - + template <class DrawMode> void updateIndexBuffer(IndexBuffer<DrawMode>& buffer, IndexVector<DrawMode>&& v) { assert(v.indexSize() == buffer.indexCount); @@ -144,6 +144,8 @@ public: return { size, createTexture(size, nullptr, format, unit) }; } + UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit); + void bindTexture(Texture&, TextureUnit = 0, TextureFilter = TextureFilter::Nearest, @@ -259,7 +261,6 @@ 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); UniqueFramebuffer createFramebuffer(); UniqueRenderbuffer createRenderbuffer(RenderbufferType, Size size); 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 <typename T, std::size_t N> struct a_data { @@ -142,6 +142,11 @@ struct a_halo_blur { using Type = gl::Attribute<float, 1>; }; +struct a_weight { + static auto name() { return "a_weight"; } + using Type = gl::Attribute<float, 1>; +}; + } // 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 <mbgl/programs/heatmap_program.hpp> + +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 <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/heatmap.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +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<int16_t> p, float ex, float ey) { + return LayoutVertex { + {{ + static_cast<int16_t>((p.x * 2) + ((ex + 1) / 2)), + static_cast<int16_t>((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 <mbgl/programs/heatmap_texture_program.hpp> + +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..9079911229 --- /dev/null +++ b/src/mbgl/programs/heatmap_texture_program.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/heatmap_texture.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/util/geometry.hpp> + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_color_ramp); +} + +class HeatmapTextureProgram : public Program< + shaders::heatmap_texture, + gl::Triangle, + gl::Attributes<attributes::a_pos>, + 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<int16_t> 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..4bb23455e8 100644 --- a/src/mbgl/programs/programs.hpp +++ b/src/mbgl/programs/programs.hpp @@ -8,6 +8,8 @@ #include <mbgl/programs/fill_extrusion_program.hpp> #include <mbgl/programs/hillshade_program.hpp> #include <mbgl/programs/hillshade_prepare_program.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/programs/heatmap_texture_program.hpp> #include <mbgl/programs/line_program.hpp> #include <mbgl/programs/raster_program.hpp> #include <mbgl/programs/symbol_program.hpp> @@ -27,6 +29,8 @@ public: fill(context, programParameters), fillExtrusion(context, programParameters), fillExtrusionPattern(context, programParameters), + heatmap(context, programParameters), + heatmapTexture(context, programParameters), fillPattern(context, programParameters), fillOutline(context, programParameters), fillOutlinePattern(context, programParameters), @@ -52,6 +56,8 @@ public: ProgramMap<FillProgram> fill; ProgramMap<FillExtrusionProgram> fillExtrusion; ProgramMap<FillExtrusionPatternProgram> fillExtrusionPattern; + ProgramMap<HeatmapProgram> heatmap; + HeatmapTextureProgram heatmapTexture; ProgramMap<FillPatternProgram> fillPattern; ProgramMap<FillOutlineProgram> fillOutline; ProgramMap<FillOutlinePatternProgram> fillOutlinePattern; diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp index 107c084918..e44d059f7f 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); +} + 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..198f977398 --- /dev/null +++ b/src/mbgl/renderer/buckets/heatmap_bucket.cpp @@ -0,0 +1,108 @@ +#include <mbgl/renderer/buckets/heatmap_bucket.hpp> +#include <mbgl/renderer/bucket_parameters.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/math.hpp> + +namespace mbgl { + +using namespace style; + +HeatmapBucket::HeatmapBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& 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<RenderHeatmapLayer>()->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<uint16_t>::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<uint16_t>::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()); + } +} + +template <class Property> +static float get(const RenderHeatmapLayer& layer, const std::map<std::string, HeatmapProgram::PaintPropertyBinders>& paintPropertyBinders) { + auto it = paintPropertyBinders.find(layer.getID()); + if (it == paintPropertyBinders.end() || !it->second.statistics<Property>().max()) { + return layer.evaluated.get<Property>().constantOr(Property::defaultValue()); + } else { + return *it->second.statistics<Property>().max(); + } +} + +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 <mbgl/renderer/bucket.hpp> +#include <mbgl/map/mode.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/gl/vertex_buffer.hpp> +#include <mbgl/gl/index_buffer.hpp> +#include <mbgl/programs/segment.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +namespace mbgl { + +class BucketParameters; + +class HeatmapBucket : public Bucket { +public: + HeatmapBucket(const BucketParameters&, const std::vector<const RenderLayer*>&); + + void addFeature(const GeometryTileFeature&, + const GeometryCollection&) override; + bool hasData() const override; + + void upload(gl::Context&) override; + + float getQueryRadius(const RenderLayer&) const override; + + gl::VertexVector<HeatmapLayoutVertex> vertices; + gl::IndexVector<gl::Triangles> triangles; + SegmentVector<HeatmapAttributes> segments; + + optional<gl::VertexBuffer<HeatmapLayoutVertex>> vertexBuffer; + optional<gl::IndexBuffer<gl::Triangles>> indexBuffer; + + std::map<std::string, HeatmapProgram::PaintPropertyBinders> 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..6f4793b31f --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -0,0 +1,163 @@ +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> +#include <mbgl/renderer/buckets/heatmap_bucket.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/renderer/paint_parameters.hpp> +#include <mbgl/renderer/render_static_data.hpp> +#include <mbgl/programs/programs.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/tile/tile.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/geometry/feature_index.hpp> +#include <mbgl/util/math.hpp> +#include <mbgl/util/intersection_tests.hpp> + +namespace mbgl { + +using namespace style; + +RenderHeatmapLayer::RenderHeatmapLayer(Immutable<style::HeatmapLayer::Impl> _impl) + : RenderLayer(style::LayerType::Heatmap, _impl), + unevaluated(impl().paint.untransitioned()) { +} + +const style::HeatmapLayer::Impl& RenderHeatmapLayer::impl() const { + return static_cast<const style::HeatmapLayer::Impl&>(*baseImpl); +} + +std::unique_ptr<Bucket> RenderHeatmapLayer::createBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers) const { + return std::make_unique<HeatmapBucket>(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<style::HeatmapOpacity>() > 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) { + renderTexture = OffscreenTexture(parameters.context, size); + } + + if (!colorRampTexture) { + const auto colorRampSize = Size{256, 1}; + colorRampTexture = gl::Texture{colorRampSize, parameters.context.createTexture(colorRampSize, colorRamp.data(), gl::TextureFormat::RGBA, 1)}; + } + + renderTexture->bind(); + + parameters.context.clear(Color{ 0.0f, 0.0f, 0.0f, 1.0f }, {}, {}); + + for (const RenderTile& tile : renderTiles) { + assert(dynamic_cast<HeatmapBucket*>(tile.tile.getBucket(*baseImpl))); + HeatmapBucket& bucket = *reinterpret_cast<HeatmapBucket*>(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<style::HeatmapIntensity>()}, + 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<HeatmapOpacity>() } }, + 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<HeatmapColor>().getValue(); + if (colorValue.isUndefined()) { + colorValue = HeatmapLayer::getDefaultHeatmapColor(); + } + + const auto size = colorRamp.size(); + + for (uint32_t i = 0; i < size; i += 4) { + const auto color = colorValue.evaluate(static_cast<double>(i) / size); + colorRamp[i + 0] = std::floor(color.r * 255); + colorRamp[i + 1] = std::floor(color.g * 255); + colorRamp[i + 2] = std::floor(color.b * 255); + colorRamp[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..413a799195 --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include <mbgl/renderer/render_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> +#include <mbgl/util/optional.hpp> +#include <mbgl/util/offscreen_texture.hpp> + +namespace mbgl { + +class RenderHeatmapLayer: public RenderLayer { +public: + RenderHeatmapLayer(Immutable<style::HeatmapLayer::Impl>); + ~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<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; + + // Paint properties + style::HeatmapPaintProperties::Unevaluated unevaluated; + style::HeatmapPaintProperties::PossiblyEvaluated evaluated; + + const style::HeatmapLayer::Impl& impl() const; + + std::array<uint8_t, 1024> colorRamp; + optional<OffscreenTexture> renderTexture; + optional<gl::Texture> colorRampTexture; +}; + +template <> +inline bool RenderLayer::is<RenderHeatmapLayer>() 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 <mbgl/renderer/layers/render_line_layer.hpp> #include <mbgl/renderer/layers/render_raster_layer.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> #include <mbgl/style/types.hpp> #include <mbgl/renderer/render_tile.hpp> @@ -35,6 +36,8 @@ std::unique_ptr<RenderLayer> RenderLayer::create(Immutable<Layer::Impl> impl) { return std::make_unique<RenderCustomLayer>(staticImmutableCast<CustomLayer::Impl>(impl)); case LayerType::FillExtrusion: return std::make_unique<RenderFillExtrusionLayer>(staticImmutableCast<FillExtrusionLayer::Impl>(impl)); + case LayerType::Heatmap: + return std::make_unique<RenderHeatmapLayer>(staticImmutableCast<HeatmapLayer::Impl>(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 61e7d17242..94f150e7df 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -15,6 +15,7 @@ #include <mbgl/renderer/layers/render_custom_layer.hpp> #include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp> #include <mbgl/renderer/layers/render_hillshade_layer.hpp> +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> #include <mbgl/renderer/style_diff.hpp> #include <mbgl/renderer/query.hpp> #include <mbgl/renderer/backend_scope.hpp> @@ -92,9 +93,9 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { // Reset zoom history state. zoomHistory.first = true; } - + assert(BackendScope::exists()); - + updateParameters.annotationManager.updateData(); const bool zoomChanged = zoomHistory.update(updateParameters.transformState.getZoom(), updateParameters.timePoint); @@ -185,8 +186,13 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (layerAdded || layerChanged) { layer.transition(transitionParameters); + + if (layer.is<RenderHeatmapLayer>()) { + layer.as<RenderHeatmapLayer>()->updateColorRamp(); + } } + if (layerAdded || layerChanged || zoomChanged || layer.hasTransition()) { layer.evaluate(evaluationParameters); } @@ -290,7 +296,11 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { RenderLayer* layer = getRenderLayer(layerImpl->id); assert(layer); - if (!parameters.staticData.has3D && (layer->is<RenderFillExtrusionLayer>() || layer->is<RenderHillshadeLayer>())) { + if (!parameters.staticData.has3D && ( + layer->is<RenderFillExtrusionLayer>() || + layer->is<RenderHillshadeLayer>() || + layer->is<RenderHeatmapLayer>())) { + parameters.staticData.has3D = true; } @@ -402,7 +412,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { } placement->setRecent(parameters.timePoint); - + updateFadingTiles(); } else { placement->setStale(); @@ -425,7 +435,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { parameters.imageManager.upload(parameters.context, 0); parameters.lineAtlas.upload(parameters.context, 0); - + // Update all clipping IDs + upload buckets. for (const auto& entry : renderSources) { if (entry.second->isEnabled()) { @@ -768,7 +778,7 @@ bool Renderer::Impl::hasTransitions(TimePoint timePoint) const { if (placement->hasTransitions(timePoint)) { return true; } - + if (fadingTiles) { return true; } diff --git a/src/mbgl/shaders/heatmap.cpp b/src/mbgl/shaders/heatmap.cpp new file mode 100644 index 0000000000..52bc493fe2 --- /dev/null +++ b/src/mbgl/shaders/heatmap.cpp @@ -0,0 +1,115 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/heatmap.hpp> + +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 + + +uniform mat4 u_matrix; +uniform float u_extrude_scale; +uniform float u_radius; +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 + + + // 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 u_radius + v_extrude = S * unscaled_extrude; + + // Scale by u_radius and the zoom-based scale factor to produce actual + // mesh position + vec2 extrude = v_extrude * u_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; +uniform highp float u_radius; +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 <mbgl/shaders/heatmap_texture.hpp> + +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..4b78b8805e 100644 --- a/src/mbgl/style/conversion/layer.cpp +++ b/src/mbgl/style/conversion/layer.cpp @@ -10,6 +10,7 @@ #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> #include <mbgl/style/layers/symbol_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> namespace mbgl { namespace style { @@ -161,6 +162,8 @@ optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(c converted = convertVectorLayer<LineLayer>(*id, value, error); } else if (*type == "circle") { converted = convertVectorLayer<CircleLayer>(*id, value, error); + } else if (*type == "heatmap") { + converted = convertVectorLayer<HeatmapLayer>(*id, value, error); } else if (*type == "symbol") { converted = convertVectorLayer<SymbolLayer>(*id, value, error); } else if (*type == "raster") { 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 <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/layers/circle_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> @@ -72,6 +73,7 @@ inline auto makeLayoutPropertySetters() { + return result; } @@ -166,6 +168,17 @@ inline auto makePaintPropertySetters() { result["circle-stroke-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>; result["circle-stroke-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>; + result["heatmap-radius"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapRadius>; + result["heatmap-radius-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapRadiusTransition>; + result["heatmap-weight"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapWeight>; + result["heatmap-weight-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapWeightTransition>; + result["heatmap-intensity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapIntensity>; + result["heatmap-intensity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapIntensityTransition>; + result["heatmap-color"] = &setProperty<HeatmapLayer, HeatmapColorPropertyValue, &HeatmapLayer::setHeatmapColor>; + result["heatmap-color-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapColorTransition>; + result["heatmap-opacity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapOpacity>; + result["heatmap-opacity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapOpacityTransition>; + result["fill-extrusion-opacity"] = &setProperty<FillExtrusionLayer, PropertyValue<float>, &FillExtrusionLayer::setFillExtrusionOpacity>; result["fill-extrusion-opacity-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionOpacityTransition>; result["fill-extrusion-color"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<Color>, &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 <mbgl/style/conversion/constant.hpp> #include <mbgl/style/conversion/property_value.hpp> #include <mbgl/style/conversion/data_driven_property_value.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> #include <mbgl/style/conversion/transition_options.hpp> #include <string> 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 <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/style/layer_observer.hpp> +// for constructing default heatmap-color ramp expression from style JSON +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> + +namespace mbgl { +namespace style { + +HeatmapLayer::HeatmapLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable<Impl>(LayerType::Heatmap, layerID, sourceID)) { +} + +HeatmapLayer::HeatmapLayer(Immutable<Impl> impl_) + : Layer(std::move(impl_)) { +} + +HeatmapLayer::~HeatmapLayer() = default; + +const HeatmapLayer::Impl& HeatmapLayer::impl() const { + return static_cast<const Impl&>(*baseImpl); +} + +Mutable<HeatmapLayer::Impl> HeatmapLayer::mutableImpl() const { + return makeMutable<Impl>(impl()); +} + +std::unique_ptr<Layer> HeatmapLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = HeatmapPaintProperties::Transitionable(); + return std::make_unique<HeatmapLayer>(std::move(impl_)); +} + +void HeatmapLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) 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<float> HeatmapLayer::getDefaultHeatmapRadius() { + return { 30 }; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapRadius() const { + return impl().paint.template get<HeatmapRadius>().value; +} + +void HeatmapLayer::setHeatmapRadius(DataDrivenPropertyValue<float> value) { + if (value == getHeatmapRadius()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapRadius>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapRadiusTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapRadius>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapRadiusTransition() const { + return impl().paint.template get<HeatmapRadius>().options; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getDefaultHeatmapWeight() { + return { 1 }; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapWeight() const { + return impl().paint.template get<HeatmapWeight>().value; +} + +void HeatmapLayer::setHeatmapWeight(DataDrivenPropertyValue<float> value) { + if (value == getHeatmapWeight()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapWeight>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapWeightTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapWeight>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapWeightTransition() const { + return impl().paint.template get<HeatmapWeight>().options; +} + +PropertyValue<float> HeatmapLayer::getDefaultHeatmapIntensity() { + return { 1 }; +} + +PropertyValue<float> HeatmapLayer::getHeatmapIntensity() const { + return impl().paint.template get<HeatmapIntensity>().value; +} + +void HeatmapLayer::setHeatmapIntensity(PropertyValue<float> value) { + if (value == getHeatmapIntensity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapIntensity>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapIntensityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapIntensity>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapIntensityTransition() const { + return impl().paint.template get<HeatmapIntensity>().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<HeatmapColorPropertyValue>(rawValue, error); +} + +HeatmapColorPropertyValue HeatmapLayer::getHeatmapColor() const { + return impl().paint.template get<HeatmapColor>().value; +} + +void HeatmapLayer::setHeatmapColor(HeatmapColorPropertyValue value) { + if (value == getHeatmapColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapColorTransition() const { + return impl().paint.template get<HeatmapColor>().options; +} + +PropertyValue<float> HeatmapLayer::getDefaultHeatmapOpacity() { + return { 1 }; +} + +PropertyValue<float> HeatmapLayer::getHeatmapOpacity() const { + return impl().paint.template get<HeatmapOpacity>().value; +} + +void HeatmapLayer::setHeatmapOpacity(PropertyValue<float> value) { + if (value == getHeatmapOpacity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapOpacity>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapOpacityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapOpacity>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapOpacityTransition() const { + return impl().paint.template get<HeatmapOpacity>().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 <mbgl/style/layers/heatmap_layer_impl.hpp> + +namespace mbgl { +namespace style { + +bool HeatmapLayer::Impl::hasLayoutDifference(const Layer::Impl& other) const { + assert(dynamic_cast<const HeatmapLayer::Impl*>(&other)); + const auto& impl = static_cast<const style::HeatmapLayer::Impl&>(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 <mbgl/style/layer_impl.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +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<rapidjson::StringBuffer>&) 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 <mbgl/style/layers/heatmap_layer_properties.hpp> + +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 <mbgl/style/types.hpp> +#include <mbgl/style/layout_property.hpp> +#include <mbgl/style/paint_property.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> + +namespace mbgl { +namespace style { + +struct HeatmapRadius : DataDrivenPaintProperty<float, attributes::a_radius, uniforms::u_radius> { + static float defaultValue() { return 30; } +}; + +struct HeatmapWeight : DataDrivenPaintProperty<float, attributes::a_weight, uniforms::u_weight> { + static float defaultValue() { return 1; } +}; + +struct HeatmapIntensity : PaintProperty<float> { + static float defaultValue() { return 1; } +}; + +struct HeatmapOpacity : PaintProperty<float> { + 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 <mbgl/style/layers/<%- type.replace('-', '_') %>_layer.hpp> #include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer_impl.hpp> #include <mbgl/style/layer_observer.hpp> +<% if (type === 'heatmap') { -%> +// for constructing default heatmap-color ramp expression from style JSON +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> +<% } -%> 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 <mbgl/style/properties.hpp> #include <mbgl/style/property_value.hpp> +#include <mbgl/style/heatmap_color_property_value.hpp> #include <mbgl/style/data_driven_property_value.hpp> #include <mbgl/renderer/property_evaluator.hpp> #include <mbgl/renderer/cross_faded_property_evaluator.hpp> @@ -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<HeatmapColorPropertyValue>; + using UnevaluatedType = Transitioning<HeatmapColorPropertyValue>; + using EvaluatorType = PropertyEvaluator<Color>; + 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 <mbgl/style/layers/background_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> |