summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Agafonkin <agafonkin@gmail.com>2018-02-15 17:38:23 +0200
committerGitHub <noreply@github.com>2018-02-15 17:38:23 +0200
commit82de856c94bbc090ba30186011610da5e233e277 (patch)
treebb8d4eb20901b4ac852520465d2487f6d5729852
parent1fdec7a5c1babc0cd36a110ce372175a5252d45e (diff)
downloadqtlocation-mapboxgl-82de856c94bbc090ba30186011610da5e233e277.tar.gz
[core, ios, macos, android, node] Heatmap layer (#11046)
Co-Authored-By: Konstantin Käfer <mail@kkaefer.com> Co-Authored-By: Anand Thakker <anandthakker@users.noreply.github.com> Co-Authored-By: Minh Nguyễn <1ec5@users.noreply.github.com>
-rw-r--r--cmake/core-files.cmake20
-rw-r--r--include/mbgl/style/conversion/heatmap_color_property_value.hpp46
-rw-r--r--include/mbgl/style/heatmap_color_property_value.hpp49
-rw-r--r--include/mbgl/style/layer.hpp3
-rw-r--r--include/mbgl/style/layer_type.hpp1
-rw-r--r--include/mbgl/style/layers/heatmap_layer.hpp86
-rw-r--r--include/mbgl/style/layers/layer.hpp.ejs3
m---------mapbox-gl-js0
-rw-r--r--platform/android/CHANGELOG.md4
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java221
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java132
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java626
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml11
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java216
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml14
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml1
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml1
-rw-r--r--platform/android/config.cmake2
-rwxr-xr-xplatform/android/scripts/generate-style-code.js3
-rw-r--r--platform/android/src/style/layers/heatmap_layer.cpp134
-rw-r--r--platform/android/src/style/layers/heatmap_layer.hpp50
-rw-r--r--platform/android/src/style/layers/layer.cpp1
-rw-r--r--platform/android/src/style/layers/layers.cpp4
-rwxr-xr-xplatform/darwin/scripts/generate-style-code.js11
-rw-r--r--platform/darwin/scripts/style-spec-overrides-v8.json10
-rw-r--r--platform/darwin/src/MGLHeatmapStyleLayer.h189
-rw-r--r--platform/darwin/src/MGLHeatmapStyleLayer.mm210
-rw-r--r--platform/darwin/src/MGLStyle.mm4
-rw-r--r--platform/darwin/src/MGLStyleLayer.mm.ejs4
-rw-r--r--platform/darwin/src/MGLStyleValue_Private.h36
-rw-r--r--platform/darwin/src/MGLVectorStyleLayer.h8
-rw-r--r--platform/darwin/test/MGLDocumentationExampleTests.swift18
-rw-r--r--platform/darwin/test/MGLHeatmapColorTests.mm61
-rw-r--r--platform/darwin/test/MGLHeatmapStyleLayerTests.mm296
-rw-r--r--platform/darwin/test/MGLStyleLayerTests.mm.ejs2
-rw-r--r--platform/ios/CHANGELOG.md1
-rw-r--r--platform/ios/docs/guides/For Style Authors.md1
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj20
-rw-r--r--platform/ios/jazzy.yml1
-rw-r--r--platform/ios/src/Mapbox.h1
-rw-r--r--platform/macos/CHANGELOG.md1
-rw-r--r--platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json16
-rw-r--r--platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdfbin0 -> 1262 bytes
-rw-r--r--platform/macos/app/StyleLayerIconTransformer.m3
-rw-r--r--platform/macos/app/heatmap.json809
-rw-r--r--platform/macos/app/resources/heatmap.svg72
-rw-r--r--platform/macos/docs/guides/For Style Authors.md1
-rw-r--r--platform/macos/jazzy.yml1
-rw-r--r--platform/macos/macos.xcodeproj/project.pbxproj20
-rw-r--r--platform/macos/src/Mapbox.h1
-rw-r--r--platform/node/src/node_map.cpp1
-rw-r--r--platform/node/test/ignores.json18
-rwxr-xr-xscripts/generate-shaders.js3
-rwxr-xr-xscripts/generate-style-code.js2
-rw-r--r--scripts/style-spec.js2
-rw-r--r--src/mbgl/gl/color_mode.hpp4
-rw-r--r--src/mbgl/gl/context.cpp31
-rw-r--r--src/mbgl/gl/context.hpp24
-rw-r--r--src/mbgl/gl/types.hpp9
-rw-r--r--src/mbgl/programs/attributes.hpp7
-rw-r--r--src/mbgl/programs/heatmap_program.cpp7
-rw-r--r--src/mbgl/programs/heatmap_program.hpp49
-rw-r--r--src/mbgl/programs/heatmap_texture_program.cpp7
-rw-r--r--src/mbgl/programs/heatmap_texture_program.hpp43
-rw-r--r--src/mbgl/programs/programs.hpp6
-rw-r--r--src/mbgl/programs/uniforms.hpp5
-rw-r--r--src/mbgl/renderer/buckets/heatmap_bucket.cpp98
-rw-r--r--src/mbgl/renderer/buckets/heatmap_bucket.hpp40
-rw-r--r--src/mbgl/renderer/layers/render_heatmap_layer.cpp178
-rw-r--r--src/mbgl/renderer/layers/render_heatmap_layer.hpp48
-rw-r--r--src/mbgl/renderer/render_layer.cpp3
-rw-r--r--src/mbgl/renderer/renderer_impl.cpp11
-rw-r--r--src/mbgl/shaders/heatmap.cpp128
-rw-r--r--src/mbgl/shaders/heatmap.hpp16
-rw-r--r--src/mbgl/shaders/heatmap_texture.cpp42
-rw-r--r--src/mbgl/shaders/heatmap_texture.hpp16
-rw-r--r--src/mbgl/style/conversion/layer.cpp3
-rw-r--r--src/mbgl/style/conversion/make_property_setters.hpp13
-rw-r--r--src/mbgl/style/conversion/property_setter.hpp1
-rw-r--r--src/mbgl/style/layers/heatmap_layer.cpp239
-rw-r--r--src/mbgl/style/layers/heatmap_layer_impl.cpp15
-rw-r--r--src/mbgl/style/layers/heatmap_layer_impl.hpp21
-rw-r--r--src/mbgl/style/layers/heatmap_layer_properties.cpp9
-rw-r--r--src/mbgl/style/layers/heatmap_layer_properties.hpp40
-rw-r--r--src/mbgl/style/layers/layer.cpp.ejs12
-rw-r--r--src/mbgl/style/layers/layer_properties.hpp.ejs1
-rw-r--r--src/mbgl/style/paint_property.hpp23
-rw-r--r--src/mbgl/style/style_impl.cpp1
-rw-r--r--src/mbgl/util/offscreen_texture.cpp22
-rw-r--r--src/mbgl/util/offscreen_texture.hpp6
90 files changed, 4569 insertions, 60 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index a0f75a6a59..f24482e301 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -146,6 +146,10 @@ set(MBGL_CORE_FILES
src/mbgl/programs/fill_extrusion_program.hpp
src/mbgl/programs/fill_program.cpp
src/mbgl/programs/fill_program.hpp
+ src/mbgl/programs/heatmap_program.cpp
+ src/mbgl/programs/heatmap_program.hpp
+ src/mbgl/programs/heatmap_texture_program.cpp
+ src/mbgl/programs/heatmap_texture_program.hpp
src/mbgl/programs/hillshade_prepare_program.cpp
src/mbgl/programs/hillshade_prepare_program.hpp
src/mbgl/programs/hillshade_program.cpp
@@ -225,6 +229,8 @@ set(MBGL_CORE_FILES
src/mbgl/renderer/buckets/fill_bucket.hpp
src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp
src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp
+ src/mbgl/renderer/buckets/heatmap_bucket.cpp
+ src/mbgl/renderer/buckets/heatmap_bucket.hpp
src/mbgl/renderer/buckets/hillshade_bucket.cpp
src/mbgl/renderer/buckets/hillshade_bucket.hpp
src/mbgl/renderer/buckets/line_bucket.cpp
@@ -245,6 +251,8 @@ set(MBGL_CORE_FILES
src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp
src/mbgl/renderer/layers/render_fill_layer.cpp
src/mbgl/renderer/layers/render_fill_layer.hpp
+ src/mbgl/renderer/layers/render_heatmap_layer.cpp
+ src/mbgl/renderer/layers/render_heatmap_layer.hpp
src/mbgl/renderer/layers/render_hillshade_layer.cpp
src/mbgl/renderer/layers/render_hillshade_layer.hpp
src/mbgl/renderer/layers/render_line_layer.cpp
@@ -297,6 +305,10 @@ set(MBGL_CORE_FILES
src/mbgl/shaders/fill_outline_pattern.hpp
src/mbgl/shaders/fill_pattern.cpp
src/mbgl/shaders/fill_pattern.hpp
+ src/mbgl/shaders/heatmap.cpp
+ src/mbgl/shaders/heatmap.hpp
+ src/mbgl/shaders/heatmap_texture.cpp
+ src/mbgl/shaders/heatmap_texture.hpp
src/mbgl/shaders/hillshade.cpp
src/mbgl/shaders/hillshade.hpp
src/mbgl/shaders/hillshade_prepare.cpp
@@ -349,6 +361,7 @@ set(MBGL_CORE_FILES
include/mbgl/style/data_driven_property_value.hpp
include/mbgl/style/filter.hpp
include/mbgl/style/filter_evaluator.hpp
+ include/mbgl/style/heatmap_color_property_value.hpp
include/mbgl/style/image.hpp
include/mbgl/style/layer.hpp
include/mbgl/style/layer_type.hpp
@@ -401,6 +414,7 @@ set(MBGL_CORE_FILES
include/mbgl/style/conversion/geojson.hpp
include/mbgl/style/conversion/geojson_options.hpp
include/mbgl/style/conversion/get_json_type.hpp
+ include/mbgl/style/conversion/heatmap_color_property_value.hpp
include/mbgl/style/conversion/layer.hpp
include/mbgl/style/conversion/light.hpp
include/mbgl/style/conversion/position.hpp
@@ -495,6 +509,7 @@ set(MBGL_CORE_FILES
include/mbgl/style/layers/custom_layer.hpp
include/mbgl/style/layers/fill_extrusion_layer.hpp
include/mbgl/style/layers/fill_layer.hpp
+ include/mbgl/style/layers/heatmap_layer.hpp
include/mbgl/style/layers/hillshade_layer.hpp
include/mbgl/style/layers/line_layer.hpp
include/mbgl/style/layers/raster_layer.hpp
@@ -522,6 +537,11 @@ set(MBGL_CORE_FILES
src/mbgl/style/layers/fill_layer_impl.hpp
src/mbgl/style/layers/fill_layer_properties.cpp
src/mbgl/style/layers/fill_layer_properties.hpp
+ src/mbgl/style/layers/heatmap_layer.cpp
+ src/mbgl/style/layers/heatmap_layer_impl.cpp
+ src/mbgl/style/layers/heatmap_layer_impl.hpp
+ src/mbgl/style/layers/heatmap_layer_properties.cpp
+ src/mbgl/style/layers/heatmap_layer_properties.hpp
src/mbgl/style/layers/hillshade_layer.cpp
src/mbgl/style/layers/hillshade_layer_impl.cpp
src/mbgl/style/layers/hillshade_layer_impl.hpp
diff --git a/include/mbgl/style/conversion/heatmap_color_property_value.hpp b/include/mbgl/style/conversion/heatmap_color_property_value.hpp
new file mode 100644
index 0000000000..e3689c524c
--- /dev/null
+++ b/include/mbgl/style/conversion/heatmap_color_property_value.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <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 {};
+ }
+ assert(*expression);
+ if (!isFeatureConstant(**expression)) {
+ error = { "property expressions not supported" };
+ return {};
+ }
+ if (!isZoomConstant(**expression)) {
+ error = { "zoom expressions not supported" };
+ return {};
+ }
+ return {HeatmapColorPropertyValue(std::move(*expression))};
+ } else {
+ error = { "heatmap-color must be an expression" };
+ return {};
+ }
+ }
+};
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/heatmap_color_property_value.hpp b/include/mbgl/style/heatmap_color_property_value.hpp
new file mode 100644
index 0000000000..130639c6e2
--- /dev/null
+++ b/include/mbgl/style/heatmap_color_property_value.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <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.isUndefined() && rhs.isUndefined()) || (lhs.value && rhs.value && *(lhs.value) == *(rhs.value));
+ }
+
+ friend bool operator!=(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) {
+ return !(lhs == rhs);
+ }
+
+public:
+ HeatmapColorPropertyValue() : value(nullptr) {}
+ HeatmapColorPropertyValue(std::shared_ptr<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; }
+
+ const expression::Expression& getExpression() const { return *value; }
+};
+
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp
index 8a5a4236b3..12494f5387 100644
--- a/include/mbgl/style/layer.hpp
+++ b/include/mbgl/style/layer.hpp
@@ -23,6 +23,7 @@ class HillshadeLayer;
class BackgroundLayer;
class CustomLayer;
class FillExtrusionLayer;
+class HeatmapLayer;
class LayerObserver;
/**
@@ -93,6 +94,8 @@ public:
return std::forward<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..0987ea4d0a 100644
--- a/include/mbgl/style/layer_type.hpp
+++ b/include/mbgl/style/layer_type.hpp
@@ -13,6 +13,7 @@ enum class LayerType {
Background,
Custom,
FillExtrusion,
+ Heatmap,
};
} // namespace style
diff --git a/include/mbgl/style/layers/heatmap_layer.hpp b/include/mbgl/style/layers/heatmap_layer.hpp
new file mode 100644
index 0000000000..33d927ad38
--- /dev/null
+++ b/include/mbgl/style/layers/heatmap_layer.hpp
@@ -0,0 +1,86 @@
+// This file is generated. Do not edit.
+
+#pragma once
+
+#include <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/mapbox-gl-js b/mapbox-gl-js
-Subproject f13c86ea356c384fdab31855b9152f5bf5ef97b
+Subproject d61850a6bd84ce6d697be392c7ef6d4b5555c20
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md
index afac6d4902..fa9b1d29cc 100644
--- a/platform/android/CHANGELOG.md
+++ b/platform/android/CHANGELOG.md
@@ -2,6 +2,10 @@
Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started.
+## master
+
+ - HeatmapLayer [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046)
+
## 6.0.0-beta.2 - February 13, 2018
- Deprecate LocationEngine [#11185](https://github.com/mapbox/mapbox-gl-native/pull/11185)
- Remove LOST from SDK [11186](https://github.com/mapbox/mapbox-gl-native/pull/11186)
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java
new file mode 100644
index 0000000000..6b8fd65def
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java
@@ -0,0 +1,221 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+package com.mapbox.mapboxsdk.style.layers;
+
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+
+import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
+
+import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+
+/**
+ * A heatmap.
+ *
+ * @see <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 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 nativeGetHeatmapOpacity();
+
+ private native TransitionOptions nativeGetHeatmapOpacityTransition();
+
+ private native void nativeSetHeatmapOpacityTransition(long duration, long delay);
+
+ @Override
+ protected native void finalize() throws Throwable;
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
index 6e644c5591..18ee05e63b 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
@@ -1516,6 +1516,138 @@ 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);
+ }
+
+ /**
+ * 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..364c8d2679
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java
@@ -0,0 +1,626 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+package com.mapbox.mapboxsdk.testapp.style;
+
+import android.graphics.Color;
+import android.support.test.espresso.UiController;
+import android.support.test.runner.AndroidJUnit4;
+
+import timber.log.Timber;
+
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.style.functions.CompositeFunction;
+import com.mapbox.mapboxsdk.style.functions.CameraFunction;
+import com.mapbox.mapboxsdk.style.functions.SourceFunction;
+import com.mapbox.mapboxsdk.style.functions.stops.CategoricalStops;
+import com.mapbox.mapboxsdk.style.functions.stops.ExponentialStops;
+import com.mapbox.mapboxsdk.style.functions.stops.IdentityStops;
+import com.mapbox.mapboxsdk.style.functions.stops.IntervalStops;
+import com.mapbox.mapboxsdk.style.functions.stops.Stop;
+import com.mapbox.mapboxsdk.style.functions.stops.Stops;
+import com.mapbox.mapboxsdk.style.layers.HeatmapLayer;
+import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction;
+import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static com.mapbox.mapboxsdk.style.functions.Function.*;
+import static com.mapbox.mapboxsdk.style.functions.stops.Stop.stop;
+import static com.mapbox.mapboxsdk.style.functions.stops.Stops.*;
+import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke;
+import static org.junit.Assert.*;
+import static com.mapbox.mapboxsdk.style.layers.Property.*;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.*;
+
+import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity;
+
+/**
+ * Basic smoke tests for HeatmapLayer
+ */
+@RunWith(AndroidJUnit4.class)
+public class HeatmapLayerTest extends BaseActivityTest {
+
+ private HeatmapLayer layer;
+
+ @Override
+ protected Class getActivityClass() {
+ return EspressoTestActivity.class;
+ }
+
+ private void setupLayer() {
+ Timber.i("Retrieving layer");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ if ((layer = mapboxMap.getLayerAs("my-layer")) == null) {
+ Timber.i("Adding layer");
+ layer = new HeatmapLayer("my-layer", "composite");
+ layer.setSourceLayer("composite");
+ mapboxMap.addLayer(layer);
+ // Layer reference is now stale, get new reference
+ layer = mapboxMap.getLayerAs("my-layer");
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSetVisibility() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("Visibility");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Get initial
+ assertEquals(layer.getVisibility().getValue(), VISIBLE);
+
+ // Set
+ layer.setProperties(visibility(NONE));
+ assertEquals(layer.getVisibility().getValue(), NONE);
+ }
+ });
+ }
+
+ @Test
+ public void testSourceLayer() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("SourceLayer");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Get initial
+ assertEquals(layer.getSourceLayer(), "composite");
+
+ // Set
+ final String sourceLayer = "test";
+ layer.setSourceLayer(sourceLayer);
+ assertEquals(layer.getSourceLayer(), sourceLayer);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radiusTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapRadiusTransition(options);
+ assertEquals(layer.getHeatmapRadiusTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapRadius(0.3f));
+ assertEquals((Float) layer.getHeatmapRadius().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ zoom(
+ exponential(
+ stop(2, heatmapRadius(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsIdentitySourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(property("FeaturePropertyA", Stops.<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 testHeatmapOpacityTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacityTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapOpacityTransition(options);
+ assertEquals(layer.getHeatmapOpacityTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapOpacity(0.3f));
+ assertEquals((Float) layer.getHeatmapOpacity().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapOpacity(
+ zoom(
+ exponential(
+ stop(2, heatmapOpacity(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapOpacity());
+ assertNotNull(layer.getHeatmapOpacity().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapOpacity().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapOpacity().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).size());
+ }
+ });
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
index 2d6efc0d84..5a0493e5bd 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
@@ -789,6 +789,17 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity"/>
</activity>
+ <activity
+ android:name=".activity.style.HeatmapLayerActivity"
+ android:description="@string/description_heatmaplayer"
+ android:label="@string/activity_heatmaplayer">
+ <meta-data
+ android:name="@string/category"
+ android:value="@string/category_style"/>
+ <meta-data
+ android:name="android.support.PARENT_ACTIVITY"
+ android:value=".activity.FeatureOverviewActivity"/>
+ </activity>
<!-- For Instrumentation tests -->
<activity
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java
new file mode 100644
index 0000000000..b42734ea67
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java
@@ -0,0 +1,216 @@
+package com.mapbox.mapboxsdk.testapp.activity.style;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import com.mapbox.mapboxsdk.maps.MapView;
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.style.layers.CircleLayer;
+import com.mapbox.mapboxsdk.style.layers.HeatmapLayer;
+import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
+import com.mapbox.mapboxsdk.testapp.R;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import timber.log.Timber;
+
+import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.linear;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.stop;
+import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleOpacity;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeColor;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeWidth;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapIntensity;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapOpacity;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapRadius;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapWeight;
+
+/**
+ * Test activity showcasing the heatmap layer api.
+ */
+public class HeatmapLayerActivity extends AppCompatActivity {
+
+ private static final String EARTHQUAKE_SOURCE_URL = "https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson";
+ private static final String EARTHQUAKE_SOURCE_ID = "earthquakes";
+ private static final String HEATMAP_LAYER_ID = "earthquakes-heat";
+ private static final String HEATMAP_LAYER_SOURCE = "earthquakes";
+ private static final String CIRCLE_LAYER_ID = "earthquakes-circle";
+
+ private MapView mapView;
+ private MapboxMap mapboxMap;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_heatmaplayer);
+
+ mapView = (MapView) findViewById(R.id.mapView);
+ mapView.onCreate(savedInstanceState);
+ mapView.getMapAsync(map -> {
+ mapboxMap = map;
+ addEarthquakeSource();
+ addHeatmapLayer();
+ addCircleLayer();
+ });
+ }
+
+ private void addEarthquakeSource() {
+ try {
+ mapboxMap.addSource(new GeoJsonSource(EARTHQUAKE_SOURCE_ID, new URL(EARTHQUAKE_SOURCE_URL)));
+ } catch (MalformedURLException malformedUrlException) {
+ Timber.e(malformedUrlException, "That's not an url... ");
+ }
+ }
+
+ private void addHeatmapLayer() {
+ HeatmapLayer layer = new HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID);
+ layer.setMaxZoom(9);
+ layer.setSourceLayer(HEATMAP_LAYER_SOURCE);
+ layer.setProperties(
+
+ // TODO add heatmap color https://github.com/mapbox/mapbox-gl-native/issues/11172
+ // Color ramp for heatmap. Domain is 0 (low) to 1 (high).
+ // Begin color ramp at 0-stop with a 0-transparancy color
+ // to create a blur-like effect.
+ //heatmapColor(),
+
+ // Increase the heatmap weight based on frequency and property magnitude
+ heatmapWeight(
+ interpolate(
+ linear(), get("mag"),
+ stop(0, 0),
+ stop(6, 1)
+ )
+ ),
+
+ // Increase the heatmap color weight weight by zoom level
+ // heatmap-intensity is a multiplier on top of heatmap-weight
+ heatmapIntensity(
+ interpolate(
+ linear(), zoom(),
+ stop(0, 1),
+ stop(9, 3)
+ )
+ ),
+
+ // Adjust the heatmap radius by zoom level
+ heatmapRadius(
+ interpolate(
+ linear(), zoom(),
+ stop(0, 2),
+ stop(9, 20)
+ )
+ ),
+
+ // Transition from heatmap to circle layer by zoom level
+ heatmapOpacity(
+ interpolate(
+ linear(), zoom(),
+ stop(7, 1),
+ stop(9, 0)
+ )
+ )
+ );
+
+ mapboxMap.addLayerAbove(layer, "waterway-label");
+ }
+
+ private void addCircleLayer() {
+ CircleLayer circleLayer = new CircleLayer(CIRCLE_LAYER_ID, EARTHQUAKE_SOURCE_ID);
+ circleLayer.setProperties(
+
+ // Size circle radius by earthquake magnitude and zoom level
+ circleRadius(
+ interpolate(
+ linear(), zoom(),
+ literal(7), interpolate(
+ linear(), get("mag"),
+ stop(1, 1),
+ stop(6, 4)
+ ),
+ literal(16), interpolate(
+ linear(), get("mag"),
+ stop(1, 5),
+ stop(6, 50)
+ )
+ )
+ ),
+
+ // Color circle by earthquake magnitude
+ circleColor(
+ interpolate(
+ linear(), get("mag"),
+ literal(1), rgba(33, 102, 172, 0),
+ literal(2), rgb(103, 169, 207),
+ literal(3), rgb(209, 229, 240),
+ literal(4), rgb(253, 219, 199),
+ literal(5), rgb(239, 138, 98),
+ literal(6), rgb(178, 24, 43)
+ )
+ ),
+
+ // Transition from heatmap to circle layer by zoom level
+ circleOpacity(
+ interpolate(
+ linear(), zoom(),
+ stop(7, 0),
+ stop(8, 1)
+ )
+ ),
+ circleStrokeColor("white"),
+ circleStrokeWidth(1.0f)
+ );
+
+ mapboxMap.addLayerBelow(circleLayer, HEATMAP_LAYER_ID);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mapView.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mapView.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mapView.onStop();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mapView.onDestroy();
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml
new file mode 100644
index 0000000000..23ef9ea336
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.mapbox.mapboxsdk.maps.MapView
+ android:id="@id/mapView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:mapbox_styleUrl="@string/mapbox_style_dark"/>
+
+</RelativeLayout>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
index e867046c80..d17ec20546 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
@@ -70,4 +70,5 @@
<string name="description_grid_source">Example Custom Geometry Source</string>
<string name="description_local_glyph">Suzhou using Droid Sans for Chinese glyphs</string>
<string name="description_hillshade">Example raster-dem source and hillshade layer</string>
+ <string name="description_heatmaplayer">Use HeatmapLayer to visualise earthquakes</string>
</resources>
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
index 47fee31c0a..0323d7c428 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
@@ -70,4 +70,5 @@
<string name="activity_grid_source">Grid Source</string>
<string name="activity_local_glyph">Local CJK glyph generation</string>
<string name="activity_hillshade">Hillshade</string>
+ <string name="activity_heatmaplayer">Heatmap layer</string>
</resources> \ No newline at end of file
diff --git a/platform/android/config.cmake b/platform/android/config.cmake
index e1c36789f5..30182bbc06 100644
--- a/platform/android/config.cmake
+++ b/platform/android/config.cmake
@@ -158,6 +158,8 @@ add_library(mbgl-android STATIC
platform/android/src/style/layers/fill_extrusion_layer.hpp
platform/android/src/style/layers/fill_layer.cpp
platform/android/src/style/layers/fill_layer.hpp
+ platform/android/src/style/layers/heatmap_layer.cpp
+ platform/android/src/style/layers/heatmap_layer.hpp
platform/android/src/style/layers/hillshade_layer.cpp
platform/android/src/style/layers/hillshade_layer.hpp
platform/android/src/style/layers/layer.cpp
diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js
index 65b0a399ac..6e6d3cfa67 100755
--- a/platform/android/scripts/generate-style-code.js
+++ b/platform/android/scripts/generate-style-code.js
@@ -28,6 +28,9 @@ var layers = Object.keys(spec.layer.type.values).map((type) => {
}, []);
const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => {
+ // disabled for now, see https://github.com/mapbox/mapbox-gl-native/issues/11172
+ if (name === 'heatmap-color') return memo;
+
spec[`paint_${type}`][name].name = name;
memo.push(spec[`paint_${type}`][name]);
return memo;
diff --git a/platform/android/src/style/layers/heatmap_layer.cpp b/platform/android/src/style/layers/heatmap_layer.cpp
new file mode 100644
index 0000000000..609499ec93
--- /dev/null
+++ b/platform/android/src/style/layers/heatmap_layer.cpp
@@ -0,0 +1,134 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#include "heatmap_layer.hpp"
+
+#include <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::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::getHeatmapOpacityTransition, "nativeGetHeatmapOpacityTransition"),
+ METHOD(&HeatmapLayer::setHeatmapOpacityTransition, "nativeSetHeatmapOpacityTransition"),
+ METHOD(&HeatmapLayer::getHeatmapOpacity, "nativeGetHeatmapOpacity"));
+ }
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/style/layers/heatmap_layer.hpp b/platform/android/src/style/layers/heatmap_layer.hpp
new file mode 100644
index 0000000000..85f9f0292e
--- /dev/null
+++ b/platform/android/src/style/layers/heatmap_layer.hpp
@@ -0,0 +1,50 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#pragma once
+
+#include "layer.hpp"
+#include "../transition_options.hpp"
+#include <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> 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/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp
index da1550bdb1..29530879a5 100644
--- a/platform/android/src/style/layers/layer.cpp
+++ b/platform/android/src/style/layers/layer.cpp
@@ -10,6 +10,7 @@
#include <mbgl/style/layers/circle_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/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
diff --git a/platform/android/src/style/layers/layers.cpp b/platform/android/src/style/layers/layers.cpp
index 5d1d1bbcbf..5df689b45d 100644
--- a/platform/android/src/style/layers/layers.cpp
+++ b/platform/android/src/style/layers/layers.cpp
@@ -5,6 +5,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
@@ -16,6 +17,7 @@
#include "custom_layer.hpp"
#include "fill_extrusion_layer.hpp"
#include "fill_layer.hpp"
+#include "heatmap_layer.hpp"
#include "hillshade_layer.hpp"
#include "line_layer.hpp"
#include "raster_layer.hpp"
@@ -32,6 +34,7 @@ template <> struct PeerType<style::BackgroundLayer> { using Type = android::Back
template <> struct PeerType<style::CircleLayer> { using Type = android::CircleLayer; };
template <> struct PeerType<style::FillExtrusionLayer> { using Type = android::FillExtrusionLayer; };
template <> struct PeerType<style::FillLayer> { using Type = android::FillLayer; };
+template <> struct PeerType<style::HeatmapLayer> { using Type = android::HeatmapLayer; };
template <> struct PeerType<style::HillshadeLayer> { using Type = android::HillshadeLayer; };
template <> struct PeerType<style::LineLayer> { using Type = android::LineLayer; };
template <> struct PeerType<style::RasterLayer> { using Type = android::RasterLayer; };
@@ -95,6 +98,7 @@ void registerNativeLayers(jni::JNIEnv& env) {
CustomLayer::registerNative(env);
FillExtrusionLayer::registerNative(env);
FillLayer::registerNative(env);
+ HeatmapLayer::registerNative(env);
HillshadeLayer::registerNative(env);
LineLayer::registerNative(env);
RasterLayer::registerNative(env);
diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js
index a7804ac948..53a668d10b 100755
--- a/platform/darwin/scripts/generate-style-code.js
+++ b/platform/darwin/scripts/generate-style-code.js
@@ -308,14 +308,15 @@ global.propertyDoc = function (propertyName, property, layerType, kind) {
doc += '* Predefined functions, including mathematical and string operators\n' +
'* Conditional expressions\n' +
'* Variable assignments and references to assigned variables\n';
+ const inputVariable = property.name === 'heatmap-color' ? '$heatmapDensity' : '$zoomLevel';
if (property["property-function"]) {
- doc += '* Interpolation and step functions applied to the `$zoomLevel` variable and/or feature attributes\n';
+ doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable and/or feature attributes\n`;
} else if (property.function === "interpolated") {
- doc += '* Interpolation and step functions applied to the `$zoomLevel` variable\n\n' +
+ doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable\n\n` +
'This property does not support applying interpolation or step functions to feature attributes.';
} else {
- doc += '* Step functions applied to the `$zoomLevel` variable\n\n' +
- 'This property does not support applying interpolation functions to the `$zoomLevel` variable or applying interpolation or step functions to feature attributes.';
+ doc += `* Step functions applied to the \`${inputVariable}\` variable\n\n` +
+ `This property does not support applying interpolation functions to the \`${inputVariable}\` variable or applying interpolation or step functions to feature attributes.`;
}
}
return doc;
@@ -387,7 +388,7 @@ global.describeValue = function (value, property, layerType) {
throw new Error(`No description available for ${value[0]} expression in ${property.name} of ${layerType}.`);
}
}
-
+
switch (property.type) {
case 'boolean':
return value ? '`YES`' : '`NO`';
diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json
index 12cfa31575..b0c50a06f8 100644
--- a/platform/darwin/scripts/style-spec-overrides-v8.json
+++ b/platform/darwin/scripts/style-spec-overrides-v8.json
@@ -23,6 +23,9 @@
"circle": {
"doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use many-sided regular polygons and configure their appearance using an `MGLFillStyleLayer` object."
},
+ "heatmap": {
+ "doc": "An `MGLHeatmapStyleLayer` is a style layer that renders a <a href=\"https://en.wikipedia.org/wiki/Heat_map\">heatmap</a>.\n\nA heatmap visualizes the spatial distribution of a large, dense set of point data, using color to avoid cluttering the map with individual points at low zoom levels. The points are weighted by an attribute you specify. Use a heatmap style layer in conjunction with point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nConsider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can alternatively cluster the source using the `MGLShapeSourceOptionClustered` option and render the data using an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer`."
+ },
"raster": {
"doc": "An `MGLRasterStyleLayer` is a style layer that renders georeferenced raster imagery on the map, especially raster tiles.\n\nUse a raster style layer to configure the color parameters of raster tiles loaded by an `MGLRasterSource` object or raster images loaded by an `MGLImageSource` object. For example, you could use a raster style layer to render <a href=\"https://www.mapbox.com/satellite/\">Mapbox Satellite</a> imagery, a <a href=\"https://www.mapbox.com/help/define-tileset/#raster-tilesets\">raster tile set</a> uploaded to Mapbox Studio, or a raster map authored in <a href=\"https://tilemill-project.github.io/tilemill/\">TileMill</a>, the classic Mapbox Editor, or Mapbox Studio Classic.\n\nRaster images may also be used as icons or patterns in a style layer. To register an image for use as an icon or pattern, use the `-[MGLStyle setImage:forName:]` method. To configure a point annotation’s image, use the `MGLAnnotationImage` class."
},
@@ -79,6 +82,11 @@
"doc": "The base color of this layer. The extrusion's surfaces will be shaded differently based on this color in combination with the `light` settings. If this color is specified with an alpha component, the alpha component will be ignored; use `fill-extrusion-opacity` to set layer opacityco."
}
},
+ "paint_heatmap": {
+ "heatmap-color": {
+ "doc": "Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `$heatmapDensity` as input."
+ }
+ },
"paint_line": {
"line-pattern": {
"doc": "Name of image in style images to use for drawing image lines. For seamless patterns, image width must be a factor of two (2, 4, 8, ..., 512)."
@@ -114,4 +122,4 @@
"doc": "Distance that the text's anchor is moved from its original placement."
}
}
-} \ No newline at end of file
+}
diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.h b/platform/darwin/src/MGLHeatmapStyleLayer.h
new file mode 100644
index 0000000000..35095fd52e
--- /dev/null
+++ b/platform/darwin/src/MGLHeatmapStyleLayer.h
@@ -0,0 +1,189 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLFoundation.h"
+#import "MGLVectorStyleLayer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ An `MGLHeatmapStyleLayer` is a style layer that renders a <a
+ href="https://en.wikipedia.org/wiki/Heat_map">heatmap</a>.
+
+ A heatmap visualizes the spatial distribution of a large, dense set of point
+ data, using color to avoid cluttering the map with individual points at low
+ zoom levels. The points are weighted by an attribute you specify. Use a heatmap
+ style layer in conjunction with point or point collection features in vector
+ tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`,
+ `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature`
+ instances in an `MGLShapeSource` object.
+
+ Consider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or
+ `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point
+ data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can
+ alternatively cluster the source using the `MGLShapeSourceOptionClustered`
+ option and render the data using an `MGLCircleStyleLayer` or
+ `MGLSymbolStyleLayer`.
+
+ You can access an existing heatmap style layer using the
+ `-[MGLStyle layerWithIdentifier:]` method if you know its identifier;
+ otherwise, find it using the `MGLStyle.layers` property. You can also create a
+ new heatmap style layer and add it to the style using a method such as
+ `-[MGLStyle addLayer:]`.
+
+ ### Example
+
+ ```swift
+ let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes)
+ layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 0,
+ 6: 1])
+ layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 1,
+ 9: 3])
+ mapView.style?.addLayer(layer)
+ ```
+ */
+MGL_EXPORT
+@interface MGLHeatmapStyleLayer : MGLVectorStyleLayer
+
+/**
+ Returns a heatmap style layer initialized with an identifier and source.
+
+ After initializing and configuring the style layer, add it to a map view’s
+ style using the `-[MGLStyle addLayer:]` or
+ `-[MGLStyle insertLayer:belowLayer:]` method.
+
+ @param identifier A string that uniquely identifies the source in the style to
+ which it is added.
+ @param source The source from which to obtain the data to style. If the source
+ has not yet been added to the current style, the behavior is undefined.
+ @return An initialized foreground style layer.
+ */
+- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source;
+
+#pragma mark - Accessing the Paint Attributes
+
+/**
+ Defines the color of each point based on its density value in a heatmap. Should
+ be an expression that uses `$heatmapDensity` as input.
+
+ The default value of this property is an expression that evaluates to a rainbow
+ color scale from blue to red. Set this property to `nil` to reset it to the
+ default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant `UIColor` values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$heatmapDensity` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapColor;
+
+/**
+ Similar to `heatmapWeight` but controls the intensity of the heatmap globally.
+ Primarily used for adjusting the heatmap based on zoom level.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapIntensity;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapIntensity` property.
+
+ This property corresponds to the `heatmap-intensity-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapIntensityTransition;
+
+/**
+ The global opacity at which the heatmap layer will be drawn.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapOpacity;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapOpacity` property.
+
+ This property corresponds to the `heatmap-opacity-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapOpacityTransition;
+
+/**
+ Radius of influence of one heatmap point in points. Increasing the value makes
+ the heatmap smoother, but less detailed.
+
+ This property is measured in points.
+
+ The default value of this property is an expression that evaluates to the float
+ `30`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable and/or
+ feature attributes
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapRadius;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapRadius` property.
+
+ This property corresponds to the `heatmap-radius-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapRadiusTransition;
+
+/**
+ A measure of how much an individual point contributes to the heatmap. A value
+ of 10 would be equivalent to having 10 points of weight 1 in the same spot.
+ Especially useful when combined with clustering.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable and/or
+ feature attributes
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapWeight;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.mm b/platform/darwin/src/MGLHeatmapStyleLayer.mm
new file mode 100644
index 0000000000..a394dbda3b
--- /dev/null
+++ b/platform/darwin/src/MGLHeatmapStyleLayer.mm
@@ -0,0 +1,210 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLSource.h"
+#import "NSPredicate+MGLAdditions.h"
+#import "NSDate+MGLAdditions.h"
+#import "MGLStyleLayer_Private.h"
+#import "MGLStyleValue_Private.h"
+#import "MGLHeatmapStyleLayer.h"
+
+#include <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)setHeatmapColor:(NSExpression *)heatmapColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toPropertyValue<mbgl::style::HeatmapColorPropertyValue>(heatmapColor);
+ self.rawLayer->setHeatmapColor(mbglValue);
+}
+
+- (NSExpression *)heatmapColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapColor();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapColor();
+ }
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toExpression(propertyValue);
+}
+
+- (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..f6fc5533be 100644
--- a/platform/darwin/src/MGLStyle.mm
+++ b/platform/darwin/src/MGLStyle.mm
@@ -8,6 +8,7 @@
#import "MGLLineStyleLayer.h"
#import "MGLCircleStyleLayer.h"
#import "MGLSymbolStyleLayer.h"
+#import "MGLHeatmapStyleLayer.h"
#import "MGLHillshadeStyleLayer.h"
#import "MGLRasterStyleLayer.h"
#import "MGLBackgroundStyleLayer.h"
@@ -36,6 +37,7 @@
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/heatmap_layer.hpp>
#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/background_layer.hpp>
@@ -401,6 +403,8 @@ static NSURL *MGLStyleURL_trafficNight;
return [[MGLSymbolStyleLayer alloc] initWithRawLayer:symbolLayer];
} else if (auto rasterLayer = rawLayer->as<mbgl::style::RasterLayer>()) {
return [[MGLRasterStyleLayer alloc] initWithRawLayer:rasterLayer];
+ } else if (auto heatmapLayer = rawLayer->as<mbgl::style::HeatmapLayer>()) {
+ return [[MGLHeatmapStyleLayer alloc] initWithRawLayer:heatmapLayer];
} else if (auto hillshadeLayer = rawLayer->as<mbgl::style::HillshadeLayer>()) {
return [[MGLHillshadeStyleLayer alloc] initWithRawLayer:hillshadeLayer];
} else if (auto circleLayer = rawLayer->as<mbgl::style::CircleLayer>()) {
diff --git a/platform/darwin/src/MGLStyleLayer.mm.ejs b/platform/darwin/src/MGLStyleLayer.mm.ejs
index 41b029791f..ac7676a1cc 100644
--- a/platform/darwin/src/MGLStyleLayer.mm.ejs
+++ b/platform/darwin/src/MGLStyleLayer.mm.ejs
@@ -157,7 +157,9 @@ namespace mbgl {
- (void)set<%- camelize(property.name) %>:(NSExpression *)<%- objCName(property) %> {
MGLAssertStyleLayerIsValid();
-<% if (property["property-function"]) { -%>
+<% if (property.name === 'heatmap-color') { -%>
+ auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toPropertyValue<mbgl::style::HeatmapColorPropertyValue>(heatmapColor);
+<% } else if (property["property-function"]) { -%>
auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<<%- valueTransformerArguments(property)[0] %>>>(<%- objCName(property) %>);
<% } else { -%>
auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue<mbgl::style::PropertyValue<<%- valueTransformerArguments(property)[0] %>>>(<%- objCName(property) %>);
diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h
index ba4b413a3c..5124c29a90 100644
--- a/platform/darwin/src/MGLStyleValue_Private.h
+++ b/platform/darwin/src/MGLStyleValue_Private.h
@@ -10,6 +10,7 @@
#import "MGLConversion.h"
#include <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/position.hpp>
#import <mbgl/style/types.hpp>
@@ -52,11 +53,20 @@ public:
return mbglValue.evaluate(evaluator);
}
+ // Convert an mbgl heatmap color property value into an mgl style value
+ NSExpression *toExpression(const mbgl::style::HeatmapColorPropertyValue &mbglValue) {
+ if (mbglValue.isUndefined()) {
+ return nil;
+ }
+ return [NSExpression mgl_expressionWithJSONObject:MGLJSONObjectFromMBGLExpression(mbglValue.getExpression())];
+ }
+
/**
Converts an NSExpression to an mbgl property value.
*/
template <typename MBGLValue>
- MBGLValue toPropertyValue(NSExpression *expression) {
+ typename std::enable_if_t<!std::is_same<MBGLValue, mbgl::style::HeatmapColorPropertyValue>::value,
+ MBGLValue> toPropertyValue(NSExpression *expression) {
if (!expression) {
return {};
}
@@ -85,6 +95,30 @@ public:
return *value;
}
+
+ /**
+ Converts an NSExpression to an mbgl property value.
+ */
+ template <typename MBGLValue>
+ typename std::enable_if_t<std::is_same<MBGLValue, mbgl::style::HeatmapColorPropertyValue>::value,
+ MBGLValue> toPropertyValue(NSExpression *expression) {
+ if (!expression) {
+ return {};
+ }
+
+ NSArray *jsonExpression = expression.mgl_jsonExpressionObject;
+
+ mbgl::style::conversion::Error valueError;
+ auto value = mbgl::style::conversion::convert<mbgl::style::HeatmapColorPropertyValue>(
+ mbgl::style::conversion::makeConvertible(jsonExpression), valueError);
+ if (!value) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Invalid property value: %@", @(valueError.message.c_str())];
+ return {};
+ }
+
+ return *value;
+ }
private: // Private utilities for converting from mgl to mbgl values
diff --git a/platform/darwin/src/MGLVectorStyleLayer.h b/platform/darwin/src/MGLVectorStyleLayer.h
index 7780a34c7f..177b1b70f0 100644
--- a/platform/darwin/src/MGLVectorStyleLayer.h
+++ b/platform/darwin/src/MGLVectorStyleLayer.h
@@ -10,10 +10,10 @@ NS_ASSUME_NONNULL_BEGIN
is defined by an `MGLShapeSource` or `MGLVectorSource` object.
Create instances of `MGLCircleStyleLayer`, `MGLFillStyleLayer`,
- `MGLFillExtrusionStyleLayer`, `MGLLineStyleLayer`, and `MGLSymbolStyleLayer` in
- order to use `MGLVectorStyleLayer`'s properties and methods. Do not create
- instances of `MGLVectorStyleLayer` directly, and do not create your own
- subclasses of this class.
+ `MGLFillExtrusionStyleLayer`, `MGLHeatmapStyleLayer`, `MGLLineStyleLayer`, and
+ `MGLSymbolStyleLayer` in order to use `MGLVectorStyleLayer`'s properties and
+ methods. Do not create instances of `MGLVectorStyleLayer` directly, and do not
+ create your own subclasses of this class.
*/
MGL_EXPORT
@interface MGLVectorStyleLayer : MGLForegroundStyleLayer
diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift
index 9bf9924869..5a6e00bc4e 100644
--- a/platform/darwin/test/MGLDocumentationExampleTests.swift
+++ b/platform/darwin/test/MGLDocumentationExampleTests.swift
@@ -230,6 +230,24 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate {
XCTAssertNotNil(mapView.style?.layer(withIdentifier: "buildings"))
}
+
+ func testMGLHeatmapStyleLayer() {
+ let earthquakes = MGLShapeSource(identifier: "earthquakes", url: URL(string: "https://example.com/earthquakes.json")!, options: [:])
+ mapView.style?.addSource(earthquakes)
+
+ //#-example-code
+ let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes)
+ layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 0,
+ 6: 1])
+ layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 1,
+ 9: 3])
+ mapView.style?.addLayer(layer)
+ //#-end-example-code
+
+ XCTAssertNotNil(mapView.style?.layer(withIdentifier: "earthquake-heat"))
+ }
func testMGLSymbolStyleLayer() {
let pois = MGLVectorSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!)
diff --git a/platform/darwin/test/MGLHeatmapColorTests.mm b/platform/darwin/test/MGLHeatmapColorTests.mm
new file mode 100644
index 0000000000..8d44064d94
--- /dev/null
+++ b/platform/darwin/test/MGLHeatmapColorTests.mm
@@ -0,0 +1,61 @@
+#import <Mapbox/Mapbox.h>
+#import <XCTest/XCTest.h>
+
+#import "MGLStyleLayer_Private.h"
+
+#include <mbgl/style/layers/heatmap_layer.hpp>
+
+@interface MGLHeatmapColorTests : XCTestCase <MGLMapViewDelegate>
+@end
+
+@implementation MGLHeatmapColorTests
+
+- (void)testProperties {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+ MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+
+ auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>();
+
+ XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
+ @"heatmap-color should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapColor;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]];
+ layer.heatmapColor = constantExpression;
+
+
+ mbgl::style::PropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
+ @"Setting heatmapColor to a constant value expression should update heatmap-color.");
+ XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
+ @"heatmapColor should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]];
+ NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MGLColor blueColor]];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($heatmapDensity, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@12: constantExpression2}];
+ layer.heatmapColor = functionExpression;
+
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
+ @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
+ @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
+ XCTAssertEqualObjects(layer.heatmapColor, functionExpression,
+ @"heatmapColor should round-trip expressions depending on $heatmapDensity.");
+
+ layer.heatmapColor = nil;
+ XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
+ @"Unsetting heatmapColor should return heatmap-color to the default value.");
+ XCTAssertEqualObjects(layer.heatmapColor, defaultExpression,
+ @"heatmapColor should return the default value after being unset.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor.");
+ functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a data expression is applied to heatmapColor.");
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+}
+
+@end
diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm
new file mode 100644
index 0000000000..74121affd8
--- /dev/null
+++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm
@@ -0,0 +1,296 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLStyleLayerTests.h"
+#import "../../darwin/src/NSDate+MGLAdditions.h"
+
+#import "MGLStyleLayer_Private.h"
+
+#include <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/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs
index e26c63e662..e17501ed18 100644
--- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs
+++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs
@@ -59,6 +59,7 @@
MGLTransition transitionTest = MGLTransitionMake(5, 4);
<% for (const property of properties) { -%>
+<% if (property.name === 'heatmap-color') continue; -%>
// <%- originalPropertyName(property) %>
{
@@ -151,6 +152,7 @@
- (void)testPropertyNames {
<% for (const property of properties) { -%>
+<% if (property.name === 'heatmap-color') continue; -%>
[self testPropertyName:@"<%- property.getter || property.name %>" isBoolean:<%- property.type === 'boolean' ? 'YES' : 'NO' %>];
<% } -%>
}
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index ef27596d2b..d8b89f4d80 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -10,6 +10,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
### Styles and rendering
+* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046)
* The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726))
* Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983))
* A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642))
diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md
index 72d6f144a0..68d59fbc70 100644
--- a/platform/ios/docs/guides/For Style Authors.md
+++ b/platform/ios/docs/guides/For Style Authors.md
@@ -192,6 +192,7 @@ In style JSON | In the SDK
`circle` | `MGLCircleStyleLayer`
`fill` | `MGLFillStyleLayer`
`fill-extrusion` | `MGLFillExtrusionStyleLayer`
+`heatmap` | `MGLHeatmapStyleLayer`
`hillshade` | `MGLHillshadeStyleLayer`
`line` | `MGLLineStyleLayer`
`raster` | `MGLRasterStyleLayer`
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index e177a9bc87..07fae5945c 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -28,6 +28,8 @@
16376B471FFDB92B0000563E /* one-liner.json in Resources */ = {isa = PBXBuildFile; fileRef = DA35D0871E1A6309007DED41 /* one-liner.json */; };
16376B491FFEED010000563E /* MGLMapViewLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */; };
165D0CE720005419009A3C66 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; };
+ 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */; };
+ 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */; };
1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; };
1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; };
1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -226,6 +228,10 @@
55E2AD131E5B125400E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD121E5B125400E8C587 /* MGLOfflineStorageTests.mm */; };
632281DF1E6F855900D75A5D /* MBXEmbeddedMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */; };
6407D6701E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */; };
+ 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; };
+ 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; };
920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */; };
927FBCFC1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */; };
927FBCFF1F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -670,6 +676,8 @@
16376B3F1FFDB4B40000563E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
16376B401FFDB4B40000563E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewLayoutTests.m; sourceTree = "<group>"; };
+ 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapColorTests.mm; path = ../../darwin/test/MGLHeatmapColorTests.mm; sourceTree = "<group>"; };
+ 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapStyleLayerTests.mm; path = ../../darwin/test/MGLHeatmapStyleLayerTests.mm; sourceTree = "<group>"; };
1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; };
1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = "<group>"; };
1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = "<group>"; };
@@ -801,6 +809,8 @@
632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXEmbeddedMapViewController.h; sourceTree = "<group>"; };
632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXEmbeddedMapViewController.m; sourceTree = "<group>"; };
6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationExampleTests.swift; path = ../../darwin/test/MGLDocumentationExampleTests.swift; sourceTree = "<group>"; };
+ 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = "<group>"; };
+ 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = "<group>"; };
920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = "<group>"; };
927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXSnapshotsViewController.h; sourceTree = "<group>"; };
927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXSnapshotsViewController.m; sourceTree = "<group>"; };
@@ -1236,6 +1246,8 @@
FA68F1481E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.h */,
FA68F1491E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.mm */,
35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */,
+ 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */,
+ 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */,
35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */,
3538AA1B1D542239008EC33D /* MGLForegroundStyleLayer.h */,
3538AA1C1D542239008EC33D /* MGLForegroundStyleLayer.mm */,
@@ -1306,6 +1318,8 @@
3575798F1D513EF1000B822E /* Layers */ = {
isa = PBXGroup;
children = (
+ 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */,
+ 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */,
DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */,
DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */,
DA3C6FF21E2859E700F962BE /* test-Bridging-Header.h */,
@@ -1967,6 +1981,7 @@
DA8847F91CBAFA5100AB86E3 /* MGLPolygon.h in Headers */,
4049C2AC1DB6E05500B3F799 /* MGLPointCollection_Private.h in Headers */,
DA8847F81CBAFA5100AB86E3 /* MGLPointAnnotation.h in Headers */,
+ 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */,
353933F21D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */,
DA8847F31CBAFA5100AB86E3 /* MGLMultiPoint.h in Headers */,
30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */,
@@ -2104,6 +2119,7 @@
DABFB8631CBE99E500D62B32 /* MGLOfflineRegion.h in Headers */,
DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */,
DAF0D8141DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */,
+ 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */,
DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */,
357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */,
1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */,
@@ -2548,6 +2564,8 @@
409F43FD1E9E781C0048729D /* MGLMapViewDelegateIntegrationTests.swift in Sources */,
DA2E88651CC0382C00F24E7B /* MGLStyleTests.mm in Sources */,
DA2E88611CC0382C00F24E7B /* MGLGeometryTests.mm in Sources */,
+ 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */,
+ 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */,
357579801D501E09000B822E /* MGLFillStyleLayerTests.mm in Sources */,
35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */,
DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */,
@@ -2660,6 +2678,7 @@
35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */,
DA8848521CBAFB9800AB86E3 /* MGLAPIClient.m in Sources */,
966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
+ 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */,
DA8848301CBAFA6200AB86E3 /* NSProcessInfo+MGLAdditions.m in Sources */,
353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,
@@ -2752,6 +2771,7 @@
DAA4E4311CBB730400178DFB /* MGLMapboxEvents.m in Sources */,
966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */,
DAA4E4231CBB730400178DFB /* MGLPolygon.mm in Sources */,
+ 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */,
353AFA171D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */,
35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */,
DAA4E42A1CBB730400178DFB /* NSProcessInfo+MGLAdditions.m in Sources */,
diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml
index b3662adc70..61e9ad39e8 100644
--- a/platform/ios/jazzy.yml
+++ b/platform/ios/jazzy.yml
@@ -95,6 +95,7 @@ custom_categories:
- MGLCircleStyleLayer
- MGLFillStyleLayer
- MGLFillExtrusionStyleLayer
+ - MGLHeatmapStyleLayer
- MGLHillshadeStyleLayer
- MGLLineStyleLayer
- MGLSymbolStyleLayer
diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h
index d105189bb8..11720ac68e 100644
--- a/platform/ios/src/Mapbox.h
+++ b/platform/ios/src/Mapbox.h
@@ -45,6 +45,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[];
#import "MGLSymbolStyleLayer.h"
#import "MGLRasterStyleLayer.h"
#import "MGLCircleStyleLayer.h"
+#import "MGLHeatmapStyleLayer.h"
#import "MGLHillshadeStyleLayer.h"
#import "MGLBackgroundStyleLayer.h"
#import "MGLOpenGLStyleLayer.h"
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
index 0c8c59700d..1f671cb82a 100644
--- a/platform/macos/CHANGELOG.md
+++ b/platform/macos/CHANGELOG.md
@@ -4,6 +4,7 @@
### Styles and rendering
+* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046)
* The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726))
* Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983))
* A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642))
diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json
new file mode 100644
index 0000000000..10226ca23a
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "heatmap.pdf",
+ "language-direction" : "left-to-right"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+} \ No newline at end of file
diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf
new file mode 100644
index 0000000000..88195c3735
--- /dev/null
+++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf
Binary files differ
diff --git a/platform/macos/app/StyleLayerIconTransformer.m b/platform/macos/app/StyleLayerIconTransformer.m
index 50fe06a2c6..199de86d8b 100644
--- a/platform/macos/app/StyleLayerIconTransformer.m
+++ b/platform/macos/app/StyleLayerIconTransformer.m
@@ -31,6 +31,9 @@
if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) {
return [NSImage imageNamed:@"symbol"];
}
+ if ([layer isKindOfClass:[MGLHeatmapStyleLayer class]]) {
+ return [NSImage imageNamed:@"heatmap"];
+ }
if ([layer isKindOfClass:[MGLHillshadeStyleLayer class]]) {
return [NSImage imageNamed:@"hillshade"];
}
diff --git a/platform/macos/app/heatmap.json b/platform/macos/app/heatmap.json
new file mode 100644
index 0000000000..6469e57022
--- /dev/null
+++ b/platform/macos/app/heatmap.json
@@ -0,0 +1,809 @@
+{
+ "version": 8,
+ "name": "Basic Heatmap",
+ "center": [
+ 30.49860107152665,
+ 50.459868549177486
+ ],
+ "zoom": 14.033276876197775,
+ "bearing": 0,
+ "pitch": 0,
+ "sources": {
+ "mapbox": {
+ "url": "mapbox://mapbox.mapbox-streets-v7",
+ "type": "vector"
+ }
+ },
+ "sprite": "mapbox://sprites/mourner/cjcgg2bl16cf42snvcbbaf09z",
+ "glyphs": "mapbox://fonts/mourner/{fontstack}/{range}.pbf",
+ "layers": [
+ {
+ "id": "background",
+ "type": "background",
+ "paint": {
+ "background-color": "#dedede"
+ }
+ },
+ {
+ "id": "landuse_overlay_national_park",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "landuse_overlay",
+ "filter": [
+ "==",
+ "class",
+ "national_park"
+ ],
+ "paint": {
+ "fill-color": "#d2edae",
+ "fill-opacity": 0.75
+ }
+ },
+ {
+ "id": "landuse_park",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "landuse",
+ "filter": [
+ "==",
+ "class",
+ "park"
+ ],
+ "paint": {
+ "fill-color": "#d2edae"
+ }
+ },
+ {
+ "id": "waterway",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "waterway",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "in",
+ "class",
+ "canal",
+ "river"
+ ]
+ ],
+ "paint": {
+ "line-color": "#a0cfdf",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 8,
+ 0.5
+ ],
+ [
+ 20,
+ 15
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "water",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "water",
+ "paint": {
+ "fill-color": "#a0cfdf"
+ }
+ },
+ {
+ "id": "building",
+ "type": "fill",
+ "source": "mapbox",
+ "source-layer": "building",
+ "paint": {
+ "fill-color": "#d6d6d6"
+ }
+ },
+ {
+ "id": "tunnel_minor",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "tunnel"
+ ],
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#efefef",
+ "line-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ },
+ "line-dasharray": [
+ 0.36,
+ 0.18
+ ]
+ }
+ },
+ {
+ "id": "tunnel_major",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "tunnel"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 6,
+ 0.5
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ },
+ "line-dasharray": [
+ 0.28,
+ 0.14
+ ]
+ }
+ },
+ {
+ "id": "road_minor",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ],
+ [
+ "in",
+ "structure",
+ "ford",
+ "none"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#efefef",
+ "line-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "road_major",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ],
+ [
+ "in",
+ "structure",
+ "ford",
+ "none"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 6,
+ 0.5
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_minor case",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#dedede",
+ "line-width": {
+ "base": 1.6,
+ "stops": [
+ [
+ 12,
+ 0.5
+ ],
+ [
+ 20,
+ 10
+ ]
+ ]
+ },
+ "line-gap-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_major case",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "butt",
+ "line-join": "miter"
+ },
+ "paint": {
+ "line-color": "#dedede",
+ "line-width": {
+ "base": 1.6,
+ "stops": [
+ [
+ 12,
+ 0.5
+ ],
+ [
+ 20,
+ 10
+ ]
+ ]
+ },
+ "line-gap-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_minor",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "link",
+ "motorway_link",
+ "path",
+ "pedestrian",
+ "service",
+ "street",
+ "street_limited",
+ "track"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#efefef",
+ "line-width": {
+ "base": 1.55,
+ "stops": [
+ [
+ 4,
+ 0.25
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "bridge_major",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "road",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "==",
+ "structure",
+ "bridge"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#fff",
+ "line-width": {
+ "base": 1.4,
+ "stops": [
+ [
+ 6,
+ 0.5
+ ],
+ [
+ 20,
+ 30
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "admin_country",
+ "type": "line",
+ "source": "mapbox",
+ "source-layer": "admin",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "all",
+ [
+ "<=",
+ "admin_level",
+ 2
+ ],
+ [
+ "==",
+ "maritime",
+ 0
+ ]
+ ]
+ ],
+ "layout": {
+ "line-cap": "round",
+ "line-join": "round"
+ },
+ "paint": {
+ "line-color": "#8b8a8a",
+ "line-width": {
+ "base": 1.3,
+ "stops": [
+ [
+ 3,
+ 0.5
+ ],
+ [
+ 22,
+ 15
+ ]
+ ]
+ }
+ }
+ },
+ {
+ "id": "road_major_label",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "road_label",
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "LineString"
+ ],
+ [
+ "in",
+ "class",
+ "motorway",
+ "primary",
+ "secondary",
+ "tertiary",
+ "trunk"
+ ]
+ ],
+ "layout": {
+ "symbol-placement": "line",
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ],
+ "text-transform": "uppercase",
+ "text-letter-spacing": 0.1,
+ "text-size": {
+ "base": 1.4,
+ "stops": [
+ [
+ 10,
+ 8
+ ],
+ [
+ 20,
+ 14
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 2
+ }
+ },
+ {
+ "id": "place_label_other",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "place_label",
+ "minzoom": 8,
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "Point"
+ ],
+ [
+ "in",
+ "type",
+ "hamlet",
+ "island",
+ "neighbourhood",
+ "suburb",
+ "town",
+ "village"
+ ]
+ ],
+ "layout": {
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Semibold",
+ "Arial Unicode MS Bold"
+ ],
+ "text-max-width": 6,
+ "text-size": {
+ "stops": [
+ [
+ 6,
+ 12
+ ],
+ [
+ 12,
+ 16
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 1,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "id": "place_label_city",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "place_label",
+ "maxzoom": 16,
+ "filter": [
+ "all",
+ [
+ "==",
+ "$type",
+ "Point"
+ ],
+ [
+ "==",
+ "type",
+ "city"
+ ]
+ ],
+ "layout": {
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Bold",
+ "Arial Unicode MS Bold"
+ ],
+ "text-max-width": 10,
+ "text-size": {
+ "stops": [
+ [
+ 3,
+ 12
+ ],
+ [
+ 8,
+ 16
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 1,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "id": "country_label",
+ "type": "symbol",
+ "source": "mapbox",
+ "source-layer": "country_label",
+ "maxzoom": 12,
+ "filter": [
+ "==",
+ "$type",
+ "Point"
+ ],
+ "layout": {
+ "text-field": "{name_en}",
+ "text-font": [
+ "Open Sans Regular",
+ "Arial Unicode MS Regular"
+ ],
+ "text-max-width": 10,
+ "text-size": {
+ "stops": [
+ [
+ 3,
+ 14
+ ],
+ [
+ 8,
+ 22
+ ]
+ ]
+ }
+ },
+ "paint": {
+ "text-color": "#666",
+ "text-halo-color": "rgba(255,255,255,0.75)",
+ "text-halo-width": 1,
+ "text-halo-blur": 1
+ }
+ },
+ {
+ "id": "road-heatmap",
+ "type": "heatmap",
+ "source": "mapbox",
+ "source-layer": "road",
+ "paint": {
+ "heatmap-intensity": 1
+ }
+ }
+ ]
+}
diff --git a/platform/macos/app/resources/heatmap.svg b/platform/macos/app/resources/heatmap.svg
new file mode 100644
index 0000000000..fa2a46590a
--- /dev/null
+++ b/platform/macos/app/resources/heatmap.svg
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="16"
+ height="16"
+ viewBox="0 0 16 16"
+ id="svg4148"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="heatmap.svg"
+ inkscape:export-filename="/Users/mxn/Desktop/symbol.png"
+ inkscape:export-xdpi="90.000244"
+ inkscape:export-ydpi="90.000244">
+ <defs
+ id="defs4150" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="23.25"
+ inkscape:cx="4.7741936"
+ inkscape:cy="7.6812068"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ units="px"
+ inkscape:window-width="1280"
+ inkscape:window-height="755"
+ inkscape:window-x="0"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ inkscape:snap-bbox="true" />
+ <metadata
+ id="metadata4153">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1036.3622)">
+ <path
+ style="opacity:1;fill:#ffffff;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:0.99369001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 11.237265,1036.8591 a 4.2655011,4.1972104 0 0 0 -4.2658922,4.1979 4.2655011,4.1972104 0 0 0 0.025231,0.427 5.3408028,5.2552965 0 0 0 -1.1586611,-0.1299 5.3408028,5.2552965 0 0 0 -5.34109749,5.2556 5.3408028,5.2552965 0 0 0 5.34109749,5.2557 5.3408028,5.2552965 0 0 0 5.3410983,-5.2557 5.3408028,5.2552965 0 0 0 -0.186318,-1.3664 4.2655011,4.1972104 0 0 0 0.244542,0.012 4.2655011,4.1972104 0 0 0 4.26589,-4.198 4.2655011,4.1972104 0 0 0 -4.26589,-4.1979 z"
+ id="path818"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:#000000;fill-opacity:0.15686275;fill-rule:evenodd;stroke:none;stroke-width:0.99369001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 11.005859,1038.875 c -4.8895767,1.3679 0.321278,7.2823 2.416016,2.7578 0.450567,-1.4666 -0.923376,-2.9624 -2.416016,-2.7578 z m -5.5078121,4.5 c -5.49629855,0.4756 -2.1892763,9.0394 2.2363281,5.9121 2.667642,-1.8963 1.5392118,-6.8607 -2.2363281,-5.9121 z"
+ id="path836"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md
index 6897d640b3..de838cd78f 100644
--- a/platform/macos/docs/guides/For Style Authors.md
+++ b/platform/macos/docs/guides/For Style Authors.md
@@ -179,6 +179,7 @@ In style JSON | In the SDK
`circle` | `MGLCircleStyleLayer`
`fill` | `MGLFillStyleLayer`
`fill-extrusion` | `MGLFillExtrusionStyleLayer`
+`heatmap` | `MGLHeatmapStyleLayer`
`hillshade` | `MGLHillshadeStyleLayer`
`line` | `MGLLineStyleLayer`
`raster` | `MGLRasterStyleLayer`
diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml
index 682ccd99ff..b4dccb620f 100644
--- a/platform/macos/jazzy.yml
+++ b/platform/macos/jazzy.yml
@@ -80,6 +80,7 @@ custom_categories:
- MGLCircleStyleLayer
- MGLFillStyleLayer
- MGLFillExtrusionStyleLayer
+ - MGLHeatmapStyleLayer
- MGLHillshadeStyleLayer
- MGLLineStyleLayer
- MGLSymbolStyleLayer
diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj
index 090902fca4..4327670911 100644
--- a/platform/macos/macos.xcodeproj/project.pbxproj
+++ b/platform/macos/macos.xcodeproj/project.pbxproj
@@ -16,6 +16,8 @@
07D9474D1F67441B00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */; };
07F8E2F71F674C8800F794BB /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; };
07F8E2F81F674C9000F794BB /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */; };
+ 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */; };
+ 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */; };
1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */; };
1F7454A31ECFB00300021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */; };
1F7454A41ECFB00300021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A11ECFB00300021D39 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -85,6 +87,9 @@
55D120A31F7906E6004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; };
55D120A51F790A0C004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; };
55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */; };
+ 89462399200D199100DA8EF2 /* heatmap.json in Resources */ = {isa = PBXBuildFile; fileRef = 89462398200D199100DA8EF2 /* heatmap.json */; };
+ 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */; };
92092EF01F5EB10E00AF5130 /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; };
92092EF11F5EB10E00AF5130 /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */; };
920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */; };
@@ -298,6 +303,8 @@
07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = "<group>"; };
07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = "<group>"; };
07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = "<group>"; };
+ 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayerTests.mm; sourceTree = "<group>"; };
+ 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapColorTests.mm; sourceTree = "<group>"; };
1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; };
1F7454A01ECFB00300021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = "<group>"; };
1F7454A11ECFB00300021D39 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = "<group>"; };
@@ -369,6 +376,9 @@
55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflineStorageTests.mm; path = ../../darwin/test/MGLOfflineStorageTests.mm; sourceTree = "<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>"; };
@@ -672,6 +682,8 @@
35602BF91D3EA99F0050646F /* MGLFillStyleLayer.mm */,
35602BFD1D3EA9B40050646F /* MGLForegroundStyleLayer.h */,
35602BFE1D3EA9B40050646F /* MGLForegroundStyleLayer.mm */,
+ 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */,
+ 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */,
DAF25714201901C200367EF5 /* MGLHillshadeStyleLayer.h */,
DAF25713201901C100367EF5 /* MGLHillshadeStyleLayer.mm */,
DA8F25891D51CA540010E6B5 /* MGLLineStyleLayer.h */,
@@ -825,6 +837,7 @@
DA839EA61CC2E3400062CAFB /* Info.plist */,
96E027331E57C9A7004B8E66 /* Localizable.strings */,
DA839E981CC2E3400062CAFB /* Supporting Files */,
+ 89462398200D199100DA8EF2 /* heatmap.json */,
);
name = "Demo App";
path = app;
@@ -875,6 +888,8 @@
DA8F257C1D51C5F40010E6B5 /* Layers */ = {
isa = PBXGroup;
children = (
+ 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */,
+ 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */,
40E1601A1DF216E6005EA6D9 /* MGLStyleLayerTests.h */,
40E1601B1DF216E6005EA6D9 /* MGLStyleLayerTests.m */,
DA2207BA1DC076930002F84D /* test-Bridging-Header.h */,
@@ -1232,6 +1247,7 @@
359819591E02F611008FC139 /* NSCoder+MGLAdditions.h in Headers */,
DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */,
DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */,
+ 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */,
DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */,
DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */,
DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */,
@@ -1422,6 +1438,7 @@
DA839EA01CC2E3400062CAFB /* MapDocument.xib in Resources */,
353BAEF81D6463B8009A8DA9 /* amsterdam.geojson in Resources */,
96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */,
+ 89462399200D199100DA8EF2 /* heatmap.json in Resources */,
DA839EA51CC2E3400062CAFB /* MainMenu.xib in Resources */,
DA5589771D320C41006B7F64 /* wms.json in Resources */,
DAE6C2E21CC304F900DB3429 /* Credits.rtf in Resources */,
@@ -1503,6 +1520,7 @@
07D9474B1F6743F000E37934 /* MGLAbstractShapeSource.mm in Sources */,
DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */,
DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */,
+ 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */,
3527428E1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.mm in Sources */,
35602C011D3EA9B40050646F /* MGLForegroundStyleLayer.mm in Sources */,
408AA86A1DAEEE5D00022900 /* NSDictionary+MGLAdditions.mm in Sources */,
@@ -1565,11 +1583,13 @@
DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */,
DA87A9A41DCACC5000810D09 /* MGLSymbolStyleLayerTests.mm in Sources */,
40E1601D1DF217D6005EA6D9 /* MGLStyleLayerTests.m in Sources */,
+ 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */,
1F95931B1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm in Sources */,
DAF25721201902C100367EF5 /* MGLHillshadeStyleLayerTests.mm in Sources */,
DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.mm in Sources */,
DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */,
DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */,
+ 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */,
4031ACFC1E9EB3C100A3EA26 /* MGLMapViewDelegateIntegrationTests.swift in Sources */,
4031AD031E9FD6AA00A3EA26 /* MGLSDKTestHelpers.swift in Sources */,
DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.mm in Sources */,
diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h
index 7ea9a9dd1d..0e4b546cf7 100644
--- a/platform/macos/src/Mapbox.h
+++ b/platform/macos/src/Mapbox.h
@@ -44,6 +44,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[];
#import "MGLRasterStyleLayer.h"
#import "MGLCircleStyleLayer.h"
#import "MGLBackgroundStyleLayer.h"
+#import "MGLHeatmapStyleLayer.h"
#import "MGLHillshadeStyleLayer.h"
#import "MGLOpenGLStyleLayer.h"
#import "MGLSource.h"
diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp
index 3e66aaa789..ac14df0228 100644
--- a/platform/node/src/node_map.cpp
+++ b/platform/node/src/node_map.cpp
@@ -15,6 +15,7 @@
#include <mbgl/style/layers/circle_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/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json
index 4d6f26b056..42751da30f 100644
--- a/platform/node/test/ignores.json
+++ b/platform/node/test/ignores.json
@@ -23,24 +23,6 @@
"render-tests/fill-extrusion-pattern/opacity": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary",
"render-tests/geojson/inline-polygon-symbol": "behavior needs reconciliation with gl-js",
- "render-tests/heatmap-color/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-color/expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-color/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-intensity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-intensity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-intensity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-opacity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-opacity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-opacity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/antimeridian": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-radius/pitch30": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-weight/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-weight/identity-property-function": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
- "render-tests/heatmap-weight/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/mixed-zoom/z10-z11": "https://github.com/mapbox/mapbox-gl-native/issues/10397",
"render-tests/raster-masking/overlapping-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/10195",
"render-tests/regressions/mapbox-gl-js#2305": "https://github.com/mapbox/mapbox-gl-native/issues/6927",
diff --git a/scripts/generate-shaders.js b/scripts/generate-shaders.js
index 46c097d51a..b1eeffb8a0 100755
--- a/scripts/generate-shaders.js
+++ b/scripts/generate-shaders.js
@@ -7,9 +7,6 @@ const outputPath = 'src/mbgl/shaders';
var shaders = require('../mapbox-gl-js/src/shaders');
-delete shaders.heatmap;
-delete shaders.heatmapTexture;
-
require('./style-code');
writeIfModified(path.join(outputPath, 'preludes.hpp'), `// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js
index ff40244f98..6ddb787f19 100755
--- a/scripts/generate-style-code.js
+++ b/scripts/generate-style-code.js
@@ -97,6 +97,8 @@ global.paintPropertyType = function (property, type) {
global.propertyValueType = function (property) {
if (isDataDriven(property)) {
return `DataDrivenPropertyValue<${evaluatedType(property)}>`;
+ } else if (property.name === 'heatmap-color') {
+ return `HeatmapColorPropertyValue`;
} else {
return `PropertyValue<${evaluatedType(property)}>`;
}
diff --git a/scripts/style-spec.js b/scripts/style-spec.js
index 00daee70d0..196adc0b32 100644
--- a/scripts/style-spec.js
+++ b/scripts/style-spec.js
@@ -1,5 +1,3 @@
var spec = module.exports = require('../mapbox-gl-js/src/style-spec/reference/v8');
// Make temporary modifications here when Native doesn't have all features that JS has.
-
-delete spec.layer.type.values.heatmap;
diff --git a/src/mbgl/gl/color_mode.hpp b/src/mbgl/gl/color_mode.hpp
index c6594a3a77..e394f43501 100644
--- a/src/mbgl/gl/color_mode.hpp
+++ b/src/mbgl/gl/color_mode.hpp
@@ -85,6 +85,10 @@ public:
static ColorMode alphaBlended() {
return ColorMode { Add { One, OneMinusSrcAlpha }, {}, { true, true, true, true } };
}
+
+ static ColorMode additive() {
+ return ColorMode { Add { One, One }, {}, { true, true, true, true } };
+ }
};
constexpr bool operator!=(const ColorMode::Mask& a, const ColorMode::Mask& b) {
diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp
index b6244d58c7..f40cfa1f2c 100644
--- a/src/mbgl/gl/context.cpp
+++ b/src/mbgl/gl/context.cpp
@@ -60,6 +60,16 @@ static_assert(std::is_same<std::underlying_type_t<TextureFormat>, GLenum>::value
static_assert(underlying_type(TextureFormat::RGBA) == GL_RGBA, "OpenGL type mismatch");
static_assert(underlying_type(TextureFormat::Alpha) == GL_ALPHA, "OpenGL type mismatch");
+static_assert(std::is_same<std::underlying_type_t<TextureType>, GLenum>::value, "OpenGL type mismatch");
+static_assert(underlying_type(TextureType::UnsignedByte) == GL_UNSIGNED_BYTE, "OpenGL type mismatch");
+
+#if MBGL_USE_GLES2 && GL_HALF_FLOAT_OES
+static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_OES, "OpenGL type mismatch");
+#endif
+#if !MBGL_USE_GLES2 && GL_HALF_FLOAT_ARB
+static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_ARB, "OpenGL type mismatch");
+#endif
+
static_assert(underlying_type(UniformDataType::Float) == GL_FLOAT, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatVec2) == GL_FLOAT_VEC2, "OpenGL type mismatch");
static_assert(underlying_type(UniformDataType::FloatVec3) == GL_FLOAT_VEC3, "OpenGL type mismatch");
@@ -116,6 +126,19 @@ void Context::initializeExtensions(const std::function<gl::ProcAddress(const cha
programBinary = std::make_unique<extension::ProgramBinary>(fn);
#endif
+#if MBGL_USE_GLES2
+ constexpr const char* halfFloatExtensionName = "OES_texture_half_float";
+ constexpr const char* halfFloatColorBufferExtensionName = "EXT_color_buffer_half_float";
+#else
+ constexpr const char* halfFloatExtensionName = "ARB_half_float_pixel";
+ constexpr const char* halfFloatColorBufferExtensionName = "ARB_color_buffer_float";
+#endif
+ if (strstr(extensions, halfFloatExtensionName) != nullptr &&
+ strstr(extensions, halfFloatColorBufferExtensionName) != nullptr) {
+
+ supportsHalfFloatTextures = true;
+ }
+
if (!supportsVertexArrays()) {
Log::Warning(Event::OpenGL, "Not using Vertex Array Objects");
}
@@ -491,10 +514,10 @@ Context::createFramebuffer(const Texture& color,
}
UniqueTexture
-Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit) {
+Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) {
auto obj = createTexture();
pixelStoreUnpack = { 1 };
- updateTexture(obj, size, data, format, unit);
+ updateTexture(obj, size, data, format, unit, type);
// We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures.
// We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus.
MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
@@ -505,11 +528,11 @@ Context::createTexture(const Size size, const void* data, TextureFormat format,
}
void Context::updateTexture(
- TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit) {
+ TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) {
activeTextureUnit = unit;
texture[unit] = id;
MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLenum>(format), size.width,
- size.height, 0, static_cast<GLenum>(format), GL_UNSIGNED_BYTE,
+ size.height, 0, static_cast<GLenum>(format), static_cast<GLenum>(type),
data));
}
diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp
index 14f078367f..67624288e2 100644
--- a/src/mbgl/gl/context.hpp
+++ b/src/mbgl/gl/context.hpp
@@ -125,23 +125,29 @@ public:
// Create a texture from an image with data.
template <typename Image>
- Texture createTexture(const Image& image, TextureUnit unit = 0) {
+ Texture createTexture(const Image& image,
+ TextureUnit unit = 0,
+ TextureType type = TextureType::UnsignedByte) {
auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha;
- return { image.size, createTexture(image.size, image.data.get(), format, unit) };
+ return { image.size, createTexture(image.size, image.data.get(), format, unit, type) };
}
template <typename Image>
- void updateTexture(Texture& obj, const Image& image, TextureUnit unit = 0) {
+ void updateTexture(Texture& obj,
+ const Image& image,
+ TextureUnit unit = 0,
+ TextureType type = TextureType::UnsignedByte) {
auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha;
- updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit);
+ updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit, type);
obj.size = image.size;
}
// Creates an empty texture with the specified dimensions.
Texture createTexture(const Size size,
TextureFormat format = TextureFormat::RGBA,
- TextureUnit unit = 0) {
- return { size, createTexture(size, nullptr, format, unit) };
+ TextureUnit unit = 0,
+ TextureType type = TextureType::UnsignedByte) {
+ return { size, createTexture(size, nullptr, format, unit, type) };
}
void bindTexture(Texture&,
@@ -232,6 +238,8 @@ public:
State<value::PixelTransferStencil> pixelTransferStencil;
#endif // MBGL_USE_GLES2
+ bool supportsHalfFloatTextures = false;
+
private:
State<value::StencilFunc> stencilFunc;
State<value::StencilMask> stencilMask;
@@ -259,8 +267,8 @@ private:
void updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size);
UniqueBuffer createIndexBuffer(const void* data, std::size_t size, const BufferUsage usage);
void updateIndexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size);
- UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit);
- void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit);
+ UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit, TextureType);
+ void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit, TextureType);
UniqueFramebuffer createFramebuffer();
UniqueRenderbuffer createRenderbuffer(RenderbufferType, Size size);
std::unique_ptr<uint8_t[]> readFramebuffer(Size, TextureFormat, bool flip);
diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp
index da08195e58..376a784a0c 100644
--- a/src/mbgl/gl/types.hpp
+++ b/src/mbgl/gl/types.hpp
@@ -64,6 +64,15 @@ enum class TextureFormat : uint32_t {
#endif // MBGL_USE_GLES2
};
+enum class TextureType : uint32_t {
+ UnsignedByte = 0x1401,
+#if MBGL_USE_GLES2
+ HalfFloat = 0x8D61,
+#else
+ HalfFloat = 0x140B,
+#endif // MBGL_USE_GLES2
+};
+
enum class PrimitiveType {
Points = 0x0000,
Lines = 0x0001,
diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp
index 5d7a6474cf..c677c84d5d 100644
--- a/src/mbgl/programs/attributes.hpp
+++ b/src/mbgl/programs/attributes.hpp
@@ -30,7 +30,7 @@ MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos);
MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos);
MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_normal_ed);
MBGL_DEFINE_ATTRIBUTE(uint8_t, 1, a_fade_opacity);
-MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed);
+MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed);
template <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..7afe8060d0
--- /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);
+} // namespace uniforms
+
+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..b703323d9c 100644
--- a/src/mbgl/programs/programs.hpp
+++ b/src/mbgl/programs/programs.hpp
@@ -6,6 +6,8 @@
#include <mbgl/programs/extrusion_texture_program.hpp>
#include <mbgl/programs/fill_program.hpp>
#include <mbgl/programs/fill_extrusion_program.hpp>
+#include <mbgl/programs/heatmap_program.hpp>
+#include <mbgl/programs/heatmap_texture_program.hpp>
#include <mbgl/programs/hillshade_program.hpp>
#include <mbgl/programs/hillshade_prepare_program.hpp>
#include <mbgl/programs/line_program.hpp>
@@ -30,6 +32,8 @@ public:
fillPattern(context, programParameters),
fillOutline(context, programParameters),
fillOutlinePattern(context, programParameters),
+ heatmap(context, programParameters),
+ heatmapTexture(context, programParameters),
hillshade(context, programParameters),
hillshadePrepare(context, programParameters),
line(context, programParameters),
@@ -55,6 +59,8 @@ public:
ProgramMap<FillPatternProgram> fillPattern;
ProgramMap<FillOutlineProgram> fillOutline;
ProgramMap<FillOutlinePatternProgram> fillOutlinePattern;
+ ProgramMap<HeatmapProgram> heatmap;
+ HeatmapTextureProgram heatmapTexture;
HillshadeProgram hillshade;
HillshadePrepareProgram hillshadePrepare;
ProgramMap<LineProgram> line;
diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp
index 107c084918..071a27c808 100644
--- a/src/mbgl/programs/uniforms.hpp
+++ b/src/mbgl/programs/uniforms.hpp
@@ -37,9 +37,14 @@ MBGL_DEFINE_UNIFORM_SCALAR(Size, u_texsize);
MBGL_DEFINE_UNIFORM_SCALAR(bool, u_pitch_with_map);
MBGL_DEFINE_UNIFORM_SCALAR(float, u_camera_to_center_distance);
MBGL_DEFINE_UNIFORM_SCALAR(float, u_fade_change);
+MBGL_DEFINE_UNIFORM_SCALAR(float, u_weight);
MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_extrude_scale);
+namespace heatmap {
+MBGL_DEFINE_UNIFORM_SCALAR(float, u_extrude_scale);
+} // namespace heatmap
+
MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_a);
MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_br_a);
MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_b);
diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.cpp b/src/mbgl/renderer/buckets/heatmap_bucket.cpp
new file mode 100644
index 0000000000..a185e04ad2
--- /dev/null
+++ b/src/mbgl/renderer/buckets/heatmap_bucket.cpp
@@ -0,0 +1,98 @@
+#include <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());
+ }
+}
+
+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..0f9e3239ef
--- /dev/null
+++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp
@@ -0,0 +1,178 @@
+#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()), colorRamp({256, 1}) {
+}
+
+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) {
+ if (parameters.context.supportsHalfFloatTextures) {
+ renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::HalfFloat);
+
+ try {
+ renderTexture->bind();
+ } catch (const std::runtime_error& ex) {
+ // can't render to a half-float texture; falling back to unsigned byte one
+ renderTexture = nullopt;
+ parameters.context.supportsHalfFloatTextures = false;
+ }
+ }
+
+ if (!renderTexture) {
+ renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::UnsignedByte);
+ renderTexture->bind();
+ }
+
+ } else {
+ renderTexture->bind();
+ }
+
+ if (!colorRampTexture) {
+ colorRampTexture = parameters.context.createTexture(colorRamp, 1, gl::TextureType::UnsignedByte);
+ }
+
+ parameters.context.clear(Color{ 0.0f, 0.0f, 0.0f, 1.0f }, {}, {});
+
+ for (const RenderTile& tile : renderTiles) {
+ assert(dynamic_cast<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 length = colorRamp.bytes();
+
+ for (uint32_t i = 0; i < length; i += 4) {
+ const auto color = colorValue.evaluate(static_cast<double>(i) / length);
+ colorRamp.data[i + 0] = std::floor(color.r * 255);
+ colorRamp.data[i + 1] = std::floor(color.g * 255);
+ colorRamp.data[i + 2] = std::floor(color.b * 255);
+ colorRamp.data[i + 3] = std::floor(color.a * 255);
+ }
+
+ if (colorRampTexture) {
+ colorRampTexture = nullopt;
+ }
+}
+
+bool RenderHeatmapLayer::queryIntersectsFeature(
+ const GeometryCoordinates& queryGeometry,
+ const GeometryTileFeature& feature,
+ const float zoom,
+ const float bearing,
+ const float pixelsToTileUnits) const {
+ (void) queryGeometry;
+ (void) feature;
+ (void) zoom;
+ (void) bearing;
+ (void) pixelsToTileUnits;
+ return false;
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.hpp b/src/mbgl/renderer/layers/render_heatmap_layer.hpp
new file mode 100644
index 0000000000..3f0b1f91b4
--- /dev/null
+++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <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;
+
+ PremultipliedImage 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 26a07bb093..2ac714e122 100644
--- a/src/mbgl/renderer/renderer_impl.cpp
+++ b/src/mbgl/renderer/renderer_impl.cpp
@@ -14,6 +14,7 @@
#include <mbgl/renderer/layers/render_background_layer.hpp>
#include <mbgl/renderer/layers/render_custom_layer.hpp>
#include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp>
+#include <mbgl/renderer/layers/render_heatmap_layer.hpp>
#include <mbgl/renderer/layers/render_hillshade_layer.hpp>
#include <mbgl/renderer/style_diff.hpp>
#include <mbgl/renderer/query.hpp>
@@ -185,6 +186,10 @@ 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()) {
@@ -290,7 +295,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;
}
diff --git a/src/mbgl/shaders/heatmap.cpp b/src/mbgl/shaders/heatmap.cpp
new file mode 100644
index 0000000000..19927cfcc6
--- /dev/null
+++ b/src/mbgl/shaders/heatmap.cpp
@@ -0,0 +1,128 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#include <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
+
+
+#ifndef HAS_UNIFORM_u_radius
+uniform lowp float a_radius_t;
+attribute mediump vec2 a_radius;
+#else
+uniform mediump float u_radius;
+#endif
+
+
+uniform mat4 u_matrix;
+uniform float u_extrude_scale;
+uniform float u_opacity;
+uniform float u_intensity;
+
+attribute vec2 a_pos;
+
+varying vec2 v_extrude;
+
+// Effective "0" in the kernel density texture to adjust the kernel size to;
+// this empirically chosen number minimizes artifacts on overlapping kernels
+// for typical heatmap cases (assuming clustered source)
+const highp float ZERO = 1.0 / 255.0 / 16.0;
+
+// Gaussian kernel coefficient: 1 / sqrt(2 * PI)
+#define GAUSS_COEF 0.3989422804014327
+
+void main(void) {
+
+#ifndef HAS_UNIFORM_u_weight
+ weight = unpack_mix_vec2(a_weight, a_weight_t);
+#else
+ highp float weight = u_weight;
+#endif
+
+
+#ifndef HAS_UNIFORM_u_radius
+ mediump float radius = unpack_mix_vec2(a_radius, a_radius_t);
+#else
+ mediump float radius = u_radius;
+#endif
+
+
+ // unencode the extrusion vector that we snuck into the a_pos vector
+ vec2 unscaled_extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0);
+
+ // This 'extrude' comes in ranging from [-1, -1], to [1, 1]. We'll use
+ // it to produce the vertices of a square mesh framing the point feature
+ // we're adding to the kernel density texture. We'll also pass it as
+ // a varying, so that the fragment shader can determine the distance of
+ // each fragment from the point feature.
+ // Before we do so, we need to scale it up sufficiently so that the
+ // kernel falls effectively to zero at the edge of the mesh.
+ // That is, we want to know S such that
+ // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO
+ // Which solves to:
+ // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0
+ float S = sqrt(-2.0 * log(ZERO / weight / u_intensity / GAUSS_COEF)) / 3.0;
+
+ // Pass the varying in units of radius
+ v_extrude = S * unscaled_extrude;
+
+ // Scale by radius and the zoom-based scale factor to produce actual
+ // mesh position
+ vec2 extrude = v_extrude * radius * u_extrude_scale;
+
+ // multiply a_pos by 0.5, since we had it * 2 in order to sneak
+ // in extrusion data
+ vec4 pos = vec4(floor(a_pos * 0.5) + extrude, 0, 1);
+
+ gl_Position = u_matrix * pos;
+}
+
+)MBGL_SHADER";
+const char* heatmap::fragmentSource = R"MBGL_SHADER(
+
+#ifndef HAS_UNIFORM_u_weight
+varying highp float weight;
+#else
+uniform highp float u_weight;
+#endif
+
+
+uniform highp float u_intensity;
+varying vec2 v_extrude;
+
+// Gaussian kernel coefficient: 1 / sqrt(2 * PI)
+#define GAUSS_COEF 0.3989422804014327
+
+void main() {
+
+#ifdef HAS_UNIFORM_u_weight
+ highp float weight = u_weight;
+#endif
+
+
+ // Kernel density estimation with a Gaussian kernel of size 5x5
+ float d = -0.5 * 3.0 * 3.0 * dot(v_extrude, v_extrude);
+ float val = weight * u_intensity * GAUSS_COEF * exp(d);
+
+ gl_FragColor = vec4(val, 1.0, 1.0, 1.0);
+
+#ifdef OVERDRAW_INSPECTOR
+ gl_FragColor = vec4(1.0);
+#endif
+}
+
+)MBGL_SHADER";
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/heatmap.hpp b/src/mbgl/shaders/heatmap.hpp
new file mode 100644
index 0000000000..a3c64db942
--- /dev/null
+++ b/src/mbgl/shaders/heatmap.hpp
@@ -0,0 +1,16 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#pragma once
+
+namespace mbgl {
+namespace shaders {
+
+class heatmap {
+public:
+ static const char* name;
+ static const char* vertexSource;
+ static const char* fragmentSource;
+};
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/heatmap_texture.cpp b/src/mbgl/shaders/heatmap_texture.cpp
new file mode 100644
index 0000000000..c5d35c48ae
--- /dev/null
+++ b/src/mbgl/shaders/heatmap_texture.cpp
@@ -0,0 +1,42 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#include <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..19472bc8d6 100644
--- a/src/mbgl/style/conversion/layer.cpp
+++ b/src/mbgl/style/conversion/layer.cpp
@@ -6,6 +6,7 @@
#include <mbgl/style/layers/circle_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/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
@@ -165,6 +166,8 @@ optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(c
converted = convertVectorLayer<SymbolLayer>(*id, value, error);
} else if (*type == "raster") {
converted = convertRasterLayer(*id, value, error);
+ } else if (*type == "heatmap") {
+ converted = convertVectorLayer<HeatmapLayer>(*id, value, error);
} else if (*type == "hillshade") {
converted = convertHillshadeLayer(*id, value, error);
} else if (*type == "background") {
diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp
index adfcc4dd61..25c8fdb1ca 100644
--- a/src/mbgl/style/conversion/make_property_setters.hpp
+++ b/src/mbgl/style/conversion/make_property_setters.hpp
@@ -8,6 +8,7 @@
#include <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>
diff --git a/src/mbgl/util/offscreen_texture.cpp b/src/mbgl/util/offscreen_texture.cpp
index 339e74b250..03f555eae0 100644
--- a/src/mbgl/util/offscreen_texture.cpp
+++ b/src/mbgl/util/offscreen_texture.cpp
@@ -11,20 +11,21 @@ OffscreenTexture& OffscreenTexture::operator=(OffscreenTexture&&) = default;
class OffscreenTexture::Impl {
public:
- Impl(gl::Context& context_, const Size size_)
- : context(context_), size(std::move(size_)) {
+ Impl(gl::Context& context_, const Size size_, const gl::TextureType type_)
+ : context(context_), size(std::move(size_)), type(type_) {
assert(!size.isEmpty());
}
Impl(gl::Context& context_,
const Size size_,
- gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& depth_)
- : context(context_), size(std::move(size_)), depth(&depth_) {
+ gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& depth_,
+ const gl::TextureType type_)
+ : context(context_), size(std::move(size_)), depth(&depth_), type(type_) {
assert(!size.isEmpty());
}
void bind() {
if (!framebuffer) {
- texture = context.createTexture(size, gl::TextureFormat::RGBA);
+ texture = context.createTexture(size, gl::TextureFormat::RGBA, 0, type);
if (depth) {
framebuffer = context.createFramebuffer(*texture, *depth);
} else {
@@ -58,18 +59,21 @@ private:
optional<gl::Framebuffer> framebuffer;
optional<gl::Texture> texture;
gl::Renderbuffer<gl::RenderbufferType::DepthComponent>* depth = nullptr;
+ const gl::TextureType type;
};
OffscreenTexture::OffscreenTexture(gl::Context& context,
- const Size size)
- : impl(std::make_unique<Impl>(context, std::move(size))) {
+ const Size size,
+ const gl::TextureType type)
+ : impl(std::make_unique<Impl>(context, std::move(size), type)) {
assert(!size.isEmpty());
}
OffscreenTexture::OffscreenTexture(gl::Context& context,
const Size size,
- gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& renderbuffer)
- : impl(std::make_unique<Impl>(context, std::move(size), renderbuffer)) {
+ gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& renderbuffer,
+ const gl::TextureType type)
+ : impl(std::make_unique<Impl>(context, std::move(size), renderbuffer, type)) {
assert(!size.isEmpty());
}
diff --git a/src/mbgl/util/offscreen_texture.hpp b/src/mbgl/util/offscreen_texture.hpp
index 7f7e0f0338..36f24f16d3 100644
--- a/src/mbgl/util/offscreen_texture.hpp
+++ b/src/mbgl/util/offscreen_texture.hpp
@@ -12,10 +12,12 @@ class Texture;
class OffscreenTexture {
public:
OffscreenTexture(gl::Context&,
- Size size = { 256, 256 });
+ Size size = { 256, 256 },
+ gl::TextureType type = gl::TextureType::UnsignedByte);
OffscreenTexture(gl::Context&,
Size size,
- gl::Renderbuffer<gl::RenderbufferType::DepthComponent>&);
+ gl::Renderbuffer<gl::RenderbufferType::DepthComponent>&,
+ gl::TextureType type = gl::TextureType::UnsignedByte);
~OffscreenTexture();
OffscreenTexture(OffscreenTexture&&);
OffscreenTexture& operator=(OffscreenTexture&&);