diff --git a/INSTALL.md b/INSTALL.md
index fb619770a..a28348d15 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -7,50 +7,38 @@ that you can download instantly and get started with fast](https://www.mapbox.co
Still with us? These are the instructions you'll need to build Mapbox GL Native
from source on a variety of platforms and set up a development environment.
-Your journey will start with getting the source code, then installing the
-dependencies, and then setting up a development environment, which varies
-depending on your operating system and what platform you want to develop for.
+Your journey will start with installing dependencies, then getting the source code, and
+then setting up a development environment, which varies depending on your
+operating system and what platform you want to develop for.
-## 1: Getting the source
+## 1: Installing dependencies
-Clone the git repository:
+### macOS
- git clone https://github.com/mapbox/mapbox-gl-native.git
- cd mapbox-gl-native
+ 1. Install [Xcode](https://developer.apple.com/xcode/)
+ 2. Launch Xcode and install any updates
+ 3. Install [Homebrew](http://brew.sh)
+ 4. Install [Node.js](https://nodejs.org/), [CMake](https://cmake.org/), and [ccache](https://ccache.samba.org) with `brew install nodejs cmake ccache`
+ 5. Install [xcpretty](https://github.com/supermarin/xcpretty) with `[sudo] gem install xcpretty` (optional, used for prettifying command line builds)
-## 2: Installing dependencies
+### Linux
-These dependencies are required for all operating systems and all platform
-targets.
-
- - Modern C++ compiler that supports `-std=c++14`\*
- - clang++ 3.5 or later _or_
- - g++-4.9 or later
- - [CMake](https://cmake.org/) 3.1 or later (for build only)
- - [cURL](https://curl.haxx.se) (for build only)
- - [Node.js](https://nodejs.org/) 4.2.1 or later (for build only)
- - [`pkg-config`](https://wiki.freedesktop.org/www/Software/pkg-config/) (for build only)
-
-**Note**: We partially support C++14 because GCC 4.9 does not fully implement the
-final draft of the C++14 standard. More information in [DEVELOPING.md](DEVELOPING.md).
-
-Depending on your operating system and target, you'll need additional
-dependencies:
-
-### Additional dependencies for Linux
+Install the following:
+ - `clang++` 3.5 or later or `g++` 4.9 or later
+ - [git](https://git-scm.com/)
+ - [CMake](https://cmake.org/) 3.1 or later
+ - [cURL](https://curl.haxx.se)
+ - [Node.js](https://nodejs.org/) 4.2.1 or later
- [`libcurl`](http://curl.haxx.se/libcurl/) (depends on OpenSSL)
+ - [ccache](https://ccache.samba.org) (optional, improves recompilation performance)
-### Additional dependencies for macOS
+## 2: Getting the source
- - Apple Command Line Tools (available at [Apple Developer](https://developer.apple.com/download/more/))
- - [Homebrew](http://brew.sh)
- - [Cask](http://caskroom.io/) (if building for Android)
- - [xcpretty](https://github.com/supermarin/xcpretty) (`gem install xcpretty`)
+ Clone the git repository:
-### Optional dependencies
-
-- [ccache](https://ccache.samba.org) (for build only; improves recompilation performance)
+ git clone https://github.com/mapbox/mapbox-gl-native.git
+ cd mapbox-gl-native
## 3: Setting up a development environment & building
@@ -58,7 +46,7 @@ See the relevant SDK documentation for next steps:
* [Maps SDK for Android](platform/android/)
* [Maps SDK for iOS](platform/ios/)
-* [Maps SDK for iOS](platform/macos/)
+* [Maps SDK for macOS](platform/macos/)
* [Mapbox Qt SDK](platform/qt/)
* [Mapbox GL Native on Linux](platform/linux/)
* [node-mapbox-gl-native](platform/node/)
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index 24d526279..f24482e30 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
@@ -170,6 +174,7 @@ set(MBGL_CORE_FILES
include/mbgl/renderer/renderer.hpp
include/mbgl/renderer/renderer_backend.hpp
include/mbgl/renderer/renderer_frontend.hpp
+ include/mbgl/renderer/renderer_observer.hpp
src/mbgl/renderer/backend_scope.cpp
src/mbgl/renderer/bucket.hpp
src/mbgl/renderer/bucket_parameters.cpp
@@ -206,7 +211,6 @@ set(MBGL_CORE_FILES
src/mbgl/renderer/renderer_backend.cpp
src/mbgl/renderer/renderer_impl.cpp
src/mbgl/renderer/renderer_impl.hpp
- src/mbgl/renderer/renderer_observer.hpp
src/mbgl/renderer/style_diff.cpp
src/mbgl/renderer/style_diff.hpp
src/mbgl/renderer/tile_mask.hpp
@@ -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/renderer/renderer.hpp b/include/mbgl/renderer/renderer.hpp
index db28ee92f..798928087 100644
--- a/include/mbgl/renderer/renderer.hpp
+++ b/include/mbgl/renderer/renderer.hpp
@@ -48,7 +48,7 @@ public:
void dumpDebugLogs();
// Memory
- void onLowMemory();
+ void reduceMemoryUse();
private:
class Impl;
diff --git a/src/mbgl/renderer/renderer_observer.hpp b/include/mbgl/renderer/renderer_observer.hpp
similarity index 100%
rename from src/mbgl/renderer/renderer_observer.hpp
rename to include/mbgl/renderer/renderer_observer.hpp
diff --git a/include/mbgl/style/conversion/custom_geometry_source_options.hpp b/include/mbgl/style/conversion/custom_geometry_source_options.hpp
index 73b141e79..dedecd1aa 100644
--- a/include/mbgl/style/conversion/custom_geometry_source_options.hpp
+++ b/include/mbgl/style/conversion/custom_geometry_source_options.hpp
@@ -54,6 +54,26 @@ struct Converter {
}
}
+ const auto wrapValue = objectMember(value, "wrap");
+ if (wrapValue) {
+ if (toBool(*wrapValue)) {
+ options.tileOptions.wrap = static_cast(*toBool(*wrapValue));
+ } else {
+ error = { "CustomGeometrySource TileOptions wrap value must be a boolean" };
+ return {};
+ }
+ }
+
+ const auto clipValue = objectMember(value, "clip");
+ if (clipValue) {
+ if (toBool(*clipValue)) {
+ options.tileOptions.clip = static_cast(*toBool(*clipValue));
+ } else {
+ error = { "CustomGeometrySource TileOptiosn clip value must be a boolean" };
+ return {};
+ }
+ }
+
return { options };
}
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 000000000..e3689c524
--- /dev/null
+++ b/include/mbgl/style/conversion/heatmap_color_property_value.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace mbgl {
+namespace style {
+namespace conversion {
+
+template <>
+struct Converter {
+ optional operator()(const Convertible& value, Error& error) const {
+ if (isUndefined(value)) {
+ return HeatmapColorPropertyValue();
+ } else if (isExpression(value)) {
+ optional> expression = convert>(value, error, expression::type::Color);
+ if (!expression) {
+ return {};
+ }
+ assert(*expression);
+ if (!isFeatureConstant(**expression)) {
+ error = { "property expressions not supported" };
+ return {};
+ }
+ if (!isZoomConstant(**expression)) {
+ error = { "zoom expressions not supported" };
+ return {};
+ }
+ return {HeatmapColorPropertyValue(std::move(*expression))};
+ } else {
+ error = { "heatmap-color must be an expression" };
+ return {};
+ }
+ }
+};
+
+} // namespace conversion
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/heatmap_color_property_value.hpp b/include/mbgl/style/heatmap_color_property_value.hpp
new file mode 100644
index 000000000..130639c6e
--- /dev/null
+++ b/include/mbgl/style/heatmap_color_property_value.hpp
@@ -0,0 +1,49 @@
+#pragma once
+
+#include
+#include
+#include
+
+namespace mbgl {
+namespace style {
+
+/*
+ * Special-case implementation of (a subset of) the PropertyValue interface
+ * used for building the HeatmapColor paint property traits class.
+ */
+class HeatmapColorPropertyValue {
+private:
+ std::shared_ptr value;
+
+ friend bool operator==(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) {
+ return (lhs.isUndefined() && rhs.isUndefined()) || (lhs.value && rhs.value && *(lhs.value) == *(rhs.value));
+ }
+
+ friend bool operator!=(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) {
+ return !(lhs == rhs);
+ }
+
+public:
+ HeatmapColorPropertyValue() : value(nullptr) {}
+ HeatmapColorPropertyValue(std::shared_ptr value_) : value(std::move(value_)) {}
+
+ bool isUndefined() const { return value.get() == nullptr; }
+
+ // noop, needed for batch evaluation of paint property values to compile
+ template
+ Color evaluate(const Evaluator&, TimePoint = {}) const { return {}; }
+
+ Color evaluate(double heatmapDensity) const {
+ const auto result = value->evaluate(expression::EvaluationContext({}, nullptr, {heatmapDensity}));
+ return *expression::fromExpressionValue(*result);
+ }
+
+ bool isDataDriven() const { return false; }
+ bool hasDataDrivenPropertyDifference(const HeatmapColorPropertyValue&) const { return false; }
+
+ const expression::Expression& getExpression() const { return *value; }
+};
+
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp
index 8a5a4236b..12494f538 100644
--- a/include/mbgl/style/layer.hpp
+++ b/include/mbgl/style/layer.hpp
@@ -23,6 +23,7 @@ class HillshadeLayer;
class BackgroundLayer;
class CustomLayer;
class FillExtrusionLayer;
+class HeatmapLayer;
class LayerObserver;
/**
@@ -93,6 +94,8 @@ public:
return std::forward(visitor)(*as());
case LayerType::FillExtrusion:
return std::forward(visitor)(*as());
+ case LayerType::Heatmap:
+ return std::forward(visitor)(*as());
}
diff --git a/include/mbgl/style/layer_type.hpp b/include/mbgl/style/layer_type.hpp
index 951757134..0987ea4d0 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 000000000..33d927ad3
--- /dev/null
+++ b/include/mbgl/style/layers/heatmap_layer.hpp
@@ -0,0 +1,86 @@
+// This file is generated. Do not edit.
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace mbgl {
+namespace style {
+
+class TransitionOptions;
+
+class HeatmapLayer : public Layer {
+public:
+ HeatmapLayer(const std::string& layerID, const std::string& sourceID);
+ ~HeatmapLayer() final;
+
+ // Source
+ const std::string& getSourceID() const;
+ const std::string& getSourceLayer() const;
+ void setSourceLayer(const std::string& sourceLayer);
+
+ void setFilter(const Filter&);
+ const Filter& getFilter() const;
+
+ // Visibility
+ void setVisibility(VisibilityType) final;
+
+ // Zoom range
+ void setMinZoom(float) final;
+ void setMaxZoom(float) final;
+
+ // Paint properties
+
+ static DataDrivenPropertyValue getDefaultHeatmapRadius();
+ DataDrivenPropertyValue getHeatmapRadius() const;
+ void setHeatmapRadius(DataDrivenPropertyValue);
+ void setHeatmapRadiusTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapRadiusTransition() const;
+
+ static DataDrivenPropertyValue getDefaultHeatmapWeight();
+ DataDrivenPropertyValue getHeatmapWeight() const;
+ void setHeatmapWeight(DataDrivenPropertyValue);
+ void setHeatmapWeightTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapWeightTransition() const;
+
+ static PropertyValue getDefaultHeatmapIntensity();
+ PropertyValue getHeatmapIntensity() const;
+ void setHeatmapIntensity(PropertyValue);
+ void setHeatmapIntensityTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapIntensityTransition() const;
+
+ static HeatmapColorPropertyValue getDefaultHeatmapColor();
+ HeatmapColorPropertyValue getHeatmapColor() const;
+ void setHeatmapColor(HeatmapColorPropertyValue);
+ void setHeatmapColorTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapColorTransition() const;
+
+ static PropertyValue getDefaultHeatmapOpacity();
+ PropertyValue getHeatmapOpacity() const;
+ void setHeatmapOpacity(PropertyValue);
+ void setHeatmapOpacityTransition(const TransitionOptions&);
+ TransitionOptions getHeatmapOpacityTransition() const;
+
+ // Private implementation
+
+ class Impl;
+ const Impl& impl() const;
+
+ Mutable mutableImpl() const;
+ HeatmapLayer(Immutable);
+ std::unique_ptr cloneRef(const std::string& id) const final;
+};
+
+template <>
+inline bool Layer::is() const {
+ return getType() == LayerType::Heatmap;
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs
index 265dd57e1..6d40405cc 100644
--- a/include/mbgl/style/layers/layer.hpp.ejs
+++ b/include/mbgl/style/layers/layer.hpp.ejs
@@ -11,6 +11,9 @@
#include
#include
#include
+<% if (type === 'heatmap') { -%>
+#include
+<% } -%>
#include
diff --git a/include/mbgl/style/sources/custom_geometry_source.hpp b/include/mbgl/style/sources/custom_geometry_source.hpp
index a0b990b44..9daeeb381 100644
--- a/include/mbgl/style/sources/custom_geometry_source.hpp
+++ b/include/mbgl/style/sources/custom_geometry_source.hpp
@@ -25,6 +25,8 @@ public:
double tolerance = 0.375;
uint16_t tileSize = util::tileSize;
uint16_t buffer = 128;
+ bool clip = false;
+ bool wrap = false;
};
struct Options {
diff --git a/include/mbgl/util/tileset.hpp b/include/mbgl/util/tileset.hpp
index 1b7b8f0f7..ed2b90764 100644
--- a/include/mbgl/util/tileset.hpp
+++ b/include/mbgl/util/tileset.hpp
@@ -14,22 +14,28 @@ namespace mbgl {
class Tileset {
public:
enum class Scheme : bool { XYZ, TMS };
+ enum class DEMEncoding : bool { Mapbox, Terrarium };
std::vector tiles;
Range zoomRange;
std::string attribution;
Scheme scheme;
+ // DEMEncoding is not supported by the TileJSON spec
+ DEMEncoding encoding;
optional bounds;
+
Tileset(std::vector tiles_ = std::vector(),
Range zoomRange_ = { 0, util::DEFAULT_MAX_ZOOM },
std::string attribution_ = {},
- Scheme scheme_ = Scheme::XYZ)
+ Scheme scheme_ = Scheme::XYZ,
+ DEMEncoding encoding_ = DEMEncoding::Mapbox)
: tiles(std::move(tiles_)),
zoomRange(std::move(zoomRange_)),
attribution(std::move(attribution_)),
scheme(scheme_),
- bounds() {}
+ encoding(encoding_),
+ bounds() {};
// TileJSON also includes center and zoom but they are not used by mbgl.
diff --git a/mapbox-gl-js b/mapbox-gl-js
index de365184e..d61850a6b 160000
--- a/mapbox-gl-js
+++ b/mapbox-gl-js
@@ -1 +1 @@
-Subproject commit de365184e13c08fb42bbd93a08abfc8598294994
+Subproject commit d61850a6bd84ce6d697be392c7ef6d4b5555c20e
diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md
index 7b77582dd..fa9b1d29c 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)
@@ -22,7 +26,7 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to
## 5.4.0 - January 30, 2018
- Blacklist Adreno 2xx GPU for VAO support [#11047](https://github.com/mapbox/mapbox-gl-native/pull/11047)
- Bearing tracking mode GPS_NORTH_FACING [#11095](https://github.com/mapbox/mapbox-gl-native/pull/11095)
- - Disable logging missing location permissons [#11084](https://github.com/mapbox/mapbox-gl-native/pull/11084)
+ - Disable logging for missing location permissions when location is disabled [#11084](https://github.com/mapbox/mapbox-gl-native/pull/11084)
- Create offline handler using the main thread looper [#11021](https://github.com/mapbox/mapbox-gl-native/pull/11021)
## 6.0.0-beta.1 - January 26, 2018
diff --git a/platform/android/DISTRIBUTE.md b/platform/android/DISTRIBUTE.md
index 648f26ce2..1c6174832 100644
--- a/platform/android/DISTRIBUTE.md
+++ b/platform/android/DISTRIBUTE.md
@@ -13,7 +13,7 @@ BUILDTYPE=Debug or BUILDTYPE=Release
##### Creating an Android Archive file that supports all ABIs
```sh
-BUILDTYPE=RELEASE make apackage
+BUILDTYPE=Release make apackage
```
This will build native libraries to support following ABIs:
diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties
index f00eb1d77..3796173fd 100644
--- a/platform/android/MapboxGLAndroidSDK/gradle.properties
+++ b/platform/android/MapboxGLAndroidSDK/gradle.properties
@@ -1,5 +1,5 @@
GROUP=com.mapbox.mapboxsdk
-VERSION_NAME=6.0.0-SNAPSHOT
+VERSION_NAME=7.0.0-SNAPSHOT
POM_DESCRIPTION=Mapbox GL Android SDK
POM_URL=https://github.com/mapbox/mapbox-gl-native
diff --git a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
index b5a1d82c8..3b8adac5a 100644
--- a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
+++ b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro
@@ -12,4 +12,7 @@
# config for okhttp 3.8.0, https://github.com/square/okhttp/pull/3354
-dontwarn okio.**
-dontwarn javax.annotation.Nullable
--dontwarn javax.annotation.ParametersAreNonnullByDefault
\ No newline at end of file
+-dontwarn javax.annotation.ParametersAreNonnullByDefault
+
+# config for optional location provider https://github.com/mapbox/mapbox-gl-native/issues/10960
+-dontwarn com.mapzen.android.lost.api**
\ No newline at end of file
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
index a921d1403..cf70300c6 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java
@@ -42,6 +42,8 @@ import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings;
import com.mapbox.mapboxsdk.net.ConnectivityReceiver;
import com.mapbox.mapboxsdk.storage.FileSource;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -50,9 +52,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
import timber.log.Timber;
import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION;
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java
index fb2c70cb7..67ecf072e 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java
@@ -7,10 +7,10 @@ import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
import android.support.v4.view.ViewPropertyAnimatorListenerAdapter;
+import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
@@ -24,7 +24,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap;
*
*/
@SuppressLint("AppCompatCustomView")
-public final class CompassView extends ImageView implements Runnable {
+public final class CompassView extends AppCompatImageView implements Runnable {
public static final long TIME_WAIT_IDLE = 500;
public static final long TIME_MAP_NORTH_ANIMATION = 150;
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 000000000..6b8fd65de
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java
@@ -0,0 +1,221 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+package com.mapbox.mapboxsdk.style.layers;
+
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.UiThread;
+
+import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
+
+import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+
+/**
+ * A heatmap.
+ *
+ * @see The online documentation
+ */
+@UiThread
+public class HeatmapLayer extends Layer {
+
+ /**
+ * Creates a HeatmapLayer.
+ *
+ * @param nativePtr pointer used by core
+ */
+ public HeatmapLayer(long nativePtr) {
+ super(nativePtr);
+ }
+
+ /**
+ * Creates a HeatmapLayer.
+ *
+ * @param layerId the id of the layer
+ * @param sourceId the id of the source
+ */
+ public HeatmapLayer(String layerId, String sourceId) {
+ initialize(layerId, sourceId);
+ }
+
+ protected native void initialize(String layerId, String sourceId);
+
+ /**
+ * Set the source layer.
+ *
+ * @param sourceLayer the source layer to set
+ */
+ public void setSourceLayer(String sourceLayer) {
+ nativeSetSourceLayer(sourceLayer);
+ }
+
+ /**
+ * Set the source Layer.
+ *
+ * @param sourceLayer the source layer to set
+ * @return This
+ */
+ public HeatmapLayer withSourceLayer(String sourceLayer) {
+ setSourceLayer(sourceLayer);
+ return this;
+ }
+
+ /**
+ * Get the source layer.
+ *
+ * @return sourceLayer the source layer to get
+ */
+ public String getSourceLayer() {
+ return nativeGetSourceLayer();
+ }
+
+ /**
+ * Set a single filter.
+ *
+ * @param filter the filter to set
+ */
+ public void setFilter(Filter.Statement filter) {
+ nativeSetFilter(filter.toArray());
+ }
+
+ /**
+ * Set a single filter.
+ *
+ * @param filter the filter to set
+ * @return This
+ */
+ public HeatmapLayer withFilter(Filter.Statement filter) {
+ setFilter(filter);
+ return this;
+ }
+
+ /**
+ * Set a property or properties.
+ *
+ * @param properties the var-args properties
+ * @return This
+ */
+ public HeatmapLayer withProperties(@NonNull PropertyValue>... properties) {
+ setProperties(properties);
+ return this;
+ }
+
+ // Property getters
+
+ /**
+ * Get the HeatmapRadius property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue getHeatmapRadius() {
+ return (PropertyValue) new PropertyValue("heatmap-radius", nativeGetHeatmapRadius());
+ }
+
+ /**
+ * Get the HeatmapRadius property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHeatmapRadiusTransition() {
+ return nativeGetHeatmapRadiusTransition();
+ }
+
+ /**
+ * Set the HeatmapRadius property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHeatmapRadiusTransition(TransitionOptions options) {
+ nativeSetHeatmapRadiusTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HeatmapWeight property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue getHeatmapWeight() {
+ return (PropertyValue) new PropertyValue("heatmap-weight", nativeGetHeatmapWeight());
+ }
+
+ /**
+ * Get the HeatmapIntensity property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue getHeatmapIntensity() {
+ return (PropertyValue) new PropertyValue("heatmap-intensity", nativeGetHeatmapIntensity());
+ }
+
+ /**
+ * Get the HeatmapIntensity property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHeatmapIntensityTransition() {
+ return nativeGetHeatmapIntensityTransition();
+ }
+
+ /**
+ * Set the HeatmapIntensity property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHeatmapIntensityTransition(TransitionOptions options) {
+ nativeSetHeatmapIntensityTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HeatmapOpacity property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue getHeatmapOpacity() {
+ return (PropertyValue) new PropertyValue("heatmap-opacity", nativeGetHeatmapOpacity());
+ }
+
+ /**
+ * Get the HeatmapOpacity property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHeatmapOpacityTransition() {
+ return nativeGetHeatmapOpacityTransition();
+ }
+
+ /**
+ * Set the HeatmapOpacity property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHeatmapOpacityTransition(TransitionOptions options) {
+ nativeSetHeatmapOpacityTransition(options.getDuration(), options.getDelay());
+ }
+
+ private native Object nativeGetHeatmapRadius();
+
+ private native TransitionOptions nativeGetHeatmapRadiusTransition();
+
+ private native void nativeSetHeatmapRadiusTransition(long duration, long delay);
+
+ private native Object nativeGetHeatmapWeight();
+
+ private native Object nativeGetHeatmapIntensity();
+
+ private native TransitionOptions nativeGetHeatmapIntensityTransition();
+
+ private native void nativeSetHeatmapIntensityTransition(long duration, long delay);
+
+ private native Object nativeGetHeatmapOpacity();
+
+ private native TransitionOptions nativeGetHeatmapOpacityTransition();
+
+ private native void nativeSetHeatmapOpacityTransition(long duration, long delay);
+
+ @Override
+ protected native void finalize() throws Throwable;
+
+}
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java
index dc65cb508..7e3d3a777 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java
@@ -11,7 +11,7 @@ import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor;
import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
/**
- * Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB tiles
+ * Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB and Mapzen Terrarium tiles.
*
* @see The online documentation
*/
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 6e644c559..18ee05e63 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java
@@ -1515,6 +1515,138 @@ public class PropertyFactory {
return new PaintPropertyValue<>("circle-stroke-opacity", function);
}
+ /**
+ * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue heatmapRadius(Float value) {
+ return new PaintPropertyValue<>("heatmap-radius", value);
+ }
+
+ /**
+ * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue heatmapRadius(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-radius", expression);
+ }
+
+
+ /**
+ * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed.
+ *
+ * @param the function input type
+ * @param function a wrapper function for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static PropertyValue> heatmapRadius(Function function) {
+ return new PaintPropertyValue<>("heatmap-radius", function);
+ }
+
+ /**
+ * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue heatmapWeight(Float value) {
+ return new PaintPropertyValue<>("heatmap-weight", value);
+ }
+
+ /**
+ * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue heatmapWeight(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-weight", expression);
+ }
+
+
+ /**
+ * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering.
+ *
+ * @param the function input type
+ * @param function a wrapper function for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static PropertyValue> heatmapWeight(Function function) {
+ return new PaintPropertyValue<>("heatmap-weight", function);
+ }
+
+ /**
+ * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue heatmapIntensity(Float value) {
+ return new PaintPropertyValue<>("heatmap-intensity", value);
+ }
+
+ /**
+ * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue heatmapIntensity(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-intensity", expression);
+ }
+
+
+ /**
+ * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level.
+ *
+ * @param the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static PropertyValue> heatmapIntensity(CameraFunction function) {
+ return new PaintPropertyValue<>("heatmap-intensity", function);
+ }
+
+ /**
+ * The global opacity at which the heatmap layer will be drawn.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue heatmapOpacity(Float value) {
+ return new PaintPropertyValue<>("heatmap-opacity", value);
+ }
+
+ /**
+ * The global opacity at which the heatmap layer will be drawn.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue heatmapOpacity(Expression expression) {
+ return new PaintPropertyValue<>("heatmap-opacity", expression);
+ }
+
+
+ /**
+ * The global opacity at which the heatmap layer will be drawn.
+ *
+ * @param the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static PropertyValue> heatmapOpacity(CameraFunction function) {
+ return new PaintPropertyValue<>("heatmap-opacity", function);
+ }
+
/**
* The opacity of the entire fill extrusion layer. This is rendered on a per-layer, not per-feature, basis, and data-driven styling is not available.
*
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java
index 3467ed7a7..1b0999ae2 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java
@@ -37,18 +37,18 @@ public class CustomGeometrySource extends Source {
* @param provider The tile provider that returns geometry data for this source.
*/
public CustomGeometrySource(String id, GeometryTileProvider provider) {
- this(id, provider, new GeoJsonOptions());
+ this(id, provider, new CustomGeometrySourceOptions());
}
/**
- * Create a CustomGeometrySource with non-default GeoJsonOptions.
+ * Create a CustomGeometrySource with non-default CustomGeometrySourceOptions.
* Supported options are minZoom, maxZoom, buffer, and tolerance.
*
* @param id The source id.
* @param provider The tile provider that returns geometry data for this source.
- * @param options GeoJsonOptions.
+ * @param options CustomGeometrySourceOptions.
*/
- public CustomGeometrySource(String id, GeometryTileProvider provider, GeoJsonOptions options) {
+ public CustomGeometrySource(String id, GeometryTileProvider provider, CustomGeometrySourceOptions options) {
this.provider = provider;
executor = Executors.newFixedThreadPool(4);
initialize(id, options);
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java
new file mode 100644
index 000000000..4ada38c23
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java
@@ -0,0 +1,31 @@
+package com.mapbox.mapboxsdk.style.sources;
+
+/**
+ * Builder class for composing CustomGeometrySource objects.
+ */
+public class CustomGeometrySourceOptions extends GeoJsonOptions {
+
+ /**
+ * If the data includes wrapped coordinates, setting this to true unwraps the coordinates.
+ *
+ * @param wrap defaults to false
+ * @return the current instance for chaining
+ */
+ public CustomGeometrySourceOptions withWrap(boolean wrap) {
+ this.put("wrap", wrap);
+ return this;
+ }
+
+ /**
+ * If the data includes geometry outside the tile boundaries, setting this to true clips the geometry
+ * to the tile boundaries.
+ *
+ * @param clip defaults to false
+ * @return the current instance for chaining
+ */
+ public CustomGeometrySourceOptions withClip(boolean clip) {
+ this.put("clip", clip);
+ return this;
+ }
+
+}
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 000000000..364c8d267
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java
@@ -0,0 +1,626 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+package com.mapbox.mapboxsdk.testapp.style;
+
+import android.graphics.Color;
+import android.support.test.espresso.UiController;
+import android.support.test.runner.AndroidJUnit4;
+
+import timber.log.Timber;
+
+import com.mapbox.mapboxsdk.maps.MapboxMap;
+import com.mapbox.mapboxsdk.style.functions.CompositeFunction;
+import com.mapbox.mapboxsdk.style.functions.CameraFunction;
+import com.mapbox.mapboxsdk.style.functions.SourceFunction;
+import com.mapbox.mapboxsdk.style.functions.stops.CategoricalStops;
+import com.mapbox.mapboxsdk.style.functions.stops.ExponentialStops;
+import com.mapbox.mapboxsdk.style.functions.stops.IdentityStops;
+import com.mapbox.mapboxsdk.style.functions.stops.IntervalStops;
+import com.mapbox.mapboxsdk.style.functions.stops.Stop;
+import com.mapbox.mapboxsdk.style.functions.stops.Stops;
+import com.mapbox.mapboxsdk.style.layers.HeatmapLayer;
+import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction;
+import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static com.mapbox.mapboxsdk.style.functions.Function.*;
+import static com.mapbox.mapboxsdk.style.functions.stops.Stop.stop;
+import static com.mapbox.mapboxsdk.style.functions.stops.Stops.*;
+import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke;
+import static org.junit.Assert.*;
+import static com.mapbox.mapboxsdk.style.layers.Property.*;
+import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.*;
+
+import com.mapbox.mapboxsdk.style.layers.TransitionOptions;
+import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity;
+
+/**
+ * Basic smoke tests for HeatmapLayer
+ */
+@RunWith(AndroidJUnit4.class)
+public class HeatmapLayerTest extends BaseActivityTest {
+
+ private HeatmapLayer layer;
+
+ @Override
+ protected Class getActivityClass() {
+ return EspressoTestActivity.class;
+ }
+
+ private void setupLayer() {
+ Timber.i("Retrieving layer");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ if ((layer = mapboxMap.getLayerAs("my-layer")) == null) {
+ Timber.i("Adding layer");
+ layer = new HeatmapLayer("my-layer", "composite");
+ layer.setSourceLayer("composite");
+ mapboxMap.addLayer(layer);
+ // Layer reference is now stale, get new reference
+ layer = mapboxMap.getLayerAs("my-layer");
+ }
+ }
+ });
+ }
+
+ @Test
+ public void testSetVisibility() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("Visibility");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Get initial
+ assertEquals(layer.getVisibility().getValue(), VISIBLE);
+
+ // Set
+ layer.setProperties(visibility(NONE));
+ assertEquals(layer.getVisibility().getValue(), NONE);
+ }
+ });
+ }
+
+ @Test
+ public void testSourceLayer() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("SourceLayer");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Get initial
+ assertEquals(layer.getSourceLayer(), "composite");
+
+ // Set
+ final String sourceLayer = "test";
+ layer.setSourceLayer(sourceLayer);
+ assertEquals(layer.getSourceLayer(), sourceLayer);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radiusTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapRadiusTransition(options);
+ assertEquals(layer.getHeatmapRadiusTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapRadius(0.3f));
+ assertEquals((Float) layer.getHeatmapRadius().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ zoom(
+ exponential(
+ stop(2, heatmapRadius(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsIdentitySourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(property("FeaturePropertyA", Stops.identity()))
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(IdentityStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsExponentialSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ property(
+ "FeaturePropertyA",
+ exponential(
+ stop(0.3f, heatmapRadius(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapRadiusAsCategoricalSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ property(
+ "FeaturePropertyA",
+ categorical(
+ stop(1.0f, heatmapRadius(0.3f))
+ )
+ ).withDefaultValue(heatmapRadius(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(CategoricalStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue());
+ assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue());
+ assertEquals(0.3f, ((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue());
+ }
+ });
+
+ }
+
+ @Test
+ public void testHeatmapRadiusAsCompositeFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-radius");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapRadius(
+ composite(
+ "FeaturePropertyA",
+ exponential(
+ stop(0, 0.3f, heatmapRadius(0.9f))
+ ).withBase(0.5f)
+ ).withDefaultValue(heatmapRadius(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapRadius());
+ assertNotNull(layer.getHeatmapRadius().getFunction());
+ assertEquals(CompositeFunction.class, layer.getHeatmapRadius().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapRadius().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass());
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size());
+
+ ExponentialStops, Float> stops =
+ (ExponentialStops, Float>) layer.getHeatmapRadius().getFunction().getStops();
+ Stop, Float> stop = stops.iterator().next();
+ assertEquals(0f, stop.in.zoom, 0.001);
+ assertEquals(0.3f, stop.in.value, 0.001f);
+ assertEquals(0.9f, stop.out, 0.001f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapWeight(0.3f));
+ assertEquals((Float) layer.getHeatmapWeight().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ zoom(
+ exponential(
+ stop(2, heatmapWeight(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsIdentitySourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(property("FeaturePropertyA", Stops.identity()))
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(IdentityStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsExponentialSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ property(
+ "FeaturePropertyA",
+ exponential(
+ stop(0.3f, heatmapWeight(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapWeightAsCategoricalSourceFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ property(
+ "FeaturePropertyA",
+ categorical(
+ stop(1.0f, heatmapWeight(0.3f))
+ )
+ ).withDefaultValue(heatmapWeight(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(CategoricalStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue());
+ assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue());
+ assertEquals(0.3f, ((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue());
+ }
+ });
+
+ }
+
+ @Test
+ public void testHeatmapWeightAsCompositeFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-weight");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapWeight(
+ composite(
+ "FeaturePropertyA",
+ exponential(
+ stop(0, 0.3f, heatmapWeight(0.9f))
+ ).withBase(0.5f)
+ ).withDefaultValue(heatmapWeight(0.3f))
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapWeight());
+ assertNotNull(layer.getHeatmapWeight().getFunction());
+ assertEquals(CompositeFunction.class, layer.getHeatmapWeight().getFunction().getClass());
+ assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapWeight().getFunction()).getProperty());
+ assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass());
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size());
+
+ ExponentialStops, Float> stops =
+ (ExponentialStops, Float>) layer.getHeatmapWeight().getFunction().getStops();
+ Stop, Float> stop = stops.iterator().next();
+ assertEquals(0f, stop.in.zoom, 0.001);
+ assertEquals(0.3f, stop.in.value, 0.001f);
+ assertEquals(0.9f, stop.out, 0.001f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapIntensityTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-intensityTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapIntensityTransition(options);
+ assertEquals(layer.getHeatmapIntensityTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapIntensityAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-intensity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapIntensity(0.3f));
+ assertEquals((Float) layer.getHeatmapIntensity().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapIntensityAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-intensity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapIntensity(
+ zoom(
+ exponential(
+ stop(2, heatmapIntensity(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapIntensity());
+ assertNotNull(layer.getHeatmapIntensity().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapIntensity().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapIntensity().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacityTransitionOptions");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ TransitionOptions options = new TransitionOptions(300, 100);
+ layer.setHeatmapOpacityTransition(options);
+ assertEquals(layer.getHeatmapOpacityTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(heatmapOpacity(0.3f));
+ assertEquals((Float) layer.getHeatmapOpacity().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHeatmapOpacityAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("heatmap-opacity");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ heatmapOpacity(
+ zoom(
+ exponential(
+ stop(2, heatmapOpacity(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHeatmapOpacity());
+ assertNotNull(layer.getHeatmapOpacity().getFunction());
+ assertEquals(CameraFunction.class, layer.getHeatmapOpacity().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHeatmapOpacity().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).size());
+ }
+ });
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
index 9e64e03e1..0b6d2a155 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml
@@ -800,6 +800,17 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity"/>
+
+
+
+
{
+ mapboxMap = map;
+ addEarthquakeSource();
+ addHeatmapLayer();
+ addCircleLayer();
+ });
+ }
+
+ private void addEarthquakeSource() {
+ try {
+ mapboxMap.addSource(new GeoJsonSource(EARTHQUAKE_SOURCE_ID, new URL(EARTHQUAKE_SOURCE_URL)));
+ } catch (MalformedURLException malformedUrlException) {
+ Timber.e(malformedUrlException, "That's not an url... ");
+ }
+ }
+
+ private void addHeatmapLayer() {
+ HeatmapLayer layer = new HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID);
+ layer.setMaxZoom(9);
+ layer.setSourceLayer(HEATMAP_LAYER_SOURCE);
+ layer.setProperties(
+
+ // TODO add heatmap color https://github.com/mapbox/mapbox-gl-native/issues/11172
+ // Color ramp for heatmap. Domain is 0 (low) to 1 (high).
+ // Begin color ramp at 0-stop with a 0-transparancy color
+ // to create a blur-like effect.
+ //heatmapColor(),
+
+ // Increase the heatmap weight based on frequency and property magnitude
+ heatmapWeight(
+ interpolate(
+ linear(), get("mag"),
+ stop(0, 0),
+ stop(6, 1)
+ )
+ ),
+
+ // Increase the heatmap color weight weight by zoom level
+ // heatmap-intensity is a multiplier on top of heatmap-weight
+ heatmapIntensity(
+ interpolate(
+ linear(), zoom(),
+ stop(0, 1),
+ stop(9, 3)
+ )
+ ),
+
+ // Adjust the heatmap radius by zoom level
+ heatmapRadius(
+ interpolate(
+ linear(), zoom(),
+ stop(0, 2),
+ stop(9, 20)
+ )
+ ),
+
+ // Transition from heatmap to circle layer by zoom level
+ heatmapOpacity(
+ interpolate(
+ linear(), zoom(),
+ stop(7, 1),
+ stop(9, 0)
+ )
+ )
+ );
+
+ mapboxMap.addLayerAbove(layer, "waterway-label");
+ }
+
+ private void addCircleLayer() {
+ CircleLayer circleLayer = new CircleLayer(CIRCLE_LAYER_ID, EARTHQUAKE_SOURCE_ID);
+ circleLayer.setProperties(
+
+ // Size circle radius by earthquake magnitude and zoom level
+ circleRadius(
+ interpolate(
+ linear(), zoom(),
+ literal(7), interpolate(
+ linear(), get("mag"),
+ stop(1, 1),
+ stop(6, 4)
+ ),
+ literal(16), interpolate(
+ linear(), get("mag"),
+ stop(1, 5),
+ stop(6, 50)
+ )
+ )
+ ),
+
+ // Color circle by earthquake magnitude
+ circleColor(
+ interpolate(
+ linear(), get("mag"),
+ literal(1), rgba(33, 102, 172, 0),
+ literal(2), rgb(103, 169, 207),
+ literal(3), rgb(209, 229, 240),
+ literal(4), rgb(253, 219, 199),
+ literal(5), rgb(239, 138, 98),
+ literal(6), rgb(178, 24, 43)
+ )
+ ),
+
+ // Transition from heatmap to circle layer by zoom level
+ circleOpacity(
+ interpolate(
+ linear(), zoom(),
+ stop(7, 0),
+ stop(8, 1)
+ )
+ ),
+ circleStrokeColor("white"),
+ circleStrokeWidth(1.0f)
+ );
+
+ mapboxMap.addLayerBelow(circleLayer, HEATMAP_LAYER_ID);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mapView.onStart();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mapView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mapView.onPause();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mapView.onStop();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mapView.onDestroy();
+ }
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml
new file mode 100644
index 000000000..23ef9ea33
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
index 51182f958..fc0485129 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml
@@ -71,4 +71,5 @@
Example Custom Geometry Source
Suzhou using Droid Sans for Chinese glyphs
Example raster-dem source and hillshade layer
+ Use HeatmapLayer to visualise earthquakes
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
index 078ccb64a..e2bbf5af0 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml
@@ -71,4 +71,5 @@
Grid Source
Local CJK glyph generation
Hillshade
+ Heatmap layer
\ No newline at end of file
diff --git a/platform/android/config.cmake b/platform/android/config.cmake
index e1c36789f..30182bbc0 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/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json
index 2170c4d9f..1d61d9394 100644
--- a/platform/android/scripts/exclude-activity-gen.json
+++ b/platform/android/scripts/exclude-activity-gen.json
@@ -27,6 +27,7 @@
"QueryRenderedFeaturesBoxHighlightActivity",
"MultiMapActivity",
"MapInDialogActivity",
+ "SimpleMapActivity",
"ManualZoomActivity",
"MaxMinZoomActivity",
"ScrollByActivity",
@@ -34,4 +35,4 @@
"SymbolGeneratorActivity",
"TextureViewTransparentBackgroundActivity",
"SimpleMapActivity"
-]
\ No newline at end of file
+]
diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js
index 65b0a399a..6e6d3cfa6 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/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp
index afdb08a10..2a03d9de9 100644
--- a/platform/android/src/android_renderer_frontend.cpp
+++ b/platform/android/src/android_renderer_frontend.cpp
@@ -77,8 +77,8 @@ void AndroidRendererFrontend::update(std::shared_ptr params) {
mapRenderer.requestRender();
}
-void AndroidRendererFrontend::onLowMemory() {
- mapRenderer.actor().invoke(&Renderer::onLowMemory);
+void AndroidRendererFrontend::reduceMemoryUse() {
+ mapRenderer.actor().invoke(&Renderer::reduceMemoryUse);
}
std::vector AndroidRendererFrontend::querySourceFeatures(const std::string& sourceID,
diff --git a/platform/android/src/android_renderer_frontend.hpp b/platform/android/src/android_renderer_frontend.hpp
index 178870c45..b61904e38 100644
--- a/platform/android/src/android_renderer_frontend.hpp
+++ b/platform/android/src/android_renderer_frontend.hpp
@@ -39,7 +39,7 @@ public:
AnnotationIDs queryShapeAnnotations(const ScreenBox& box) const;
// Memory
- void onLowMemory();
+ void reduceMemoryUse();
private:
MapRenderer& mapRenderer;
diff --git a/platform/android/src/example_custom_layer.cpp b/platform/android/src/example_custom_layer.cpp
index 1ed68d083..6d0bd4de4 100644
--- a/platform/android/src/example_custom_layer.cpp
+++ b/platform/android/src/example_custom_layer.cpp
@@ -96,7 +96,7 @@ void nativeContextLost(void */*context*/) {
mbgl::Log::Info(mbgl::Event::General, "nativeContextLost");
}
-void nativeDenitialize(void *context) {
+void nativeDeinitialize(void *context) {
mbgl::Log::Info(mbgl::Event::General, "nativeDeinitialize");
delete reinterpret_cast(context);
}
@@ -132,7 +132,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
env->SetStaticLongField(customLayerClass,
env->GetStaticFieldID(customLayerClass, "DeinitializeFunction", "J"),
- reinterpret_cast(nativeDenitialize));
+ reinterpret_cast(nativeDeinitialize));
return JNI_VERSION_1_6;
}
diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp
index eefb578f3..3bd46631a 100755
--- a/platform/android/src/native_map_view.cpp
+++ b/platform/android/src/native_map_view.cpp
@@ -450,7 +450,7 @@ jni::Array NativeMapView::addMarkers(jni::JNIEnv& env, jni::ArrayonLowMemory();
+ rendererFrontend->reduceMemoryUse();
}
using DebugOptions = mbgl::MapDebugOptions;
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 000000000..609499ec9
--- /dev/null
+++ b/platform/android/src/style/layers/heatmap_layer.cpp
@@ -0,0 +1,134 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#include "heatmap_layer.hpp"
+
+#include
+
+#include "../conversion/property_value.hpp"
+#include "../conversion/transition_options.hpp"
+
+namespace mbgl {
+namespace android {
+
+ /**
+ * Creates an owning peer object (for layers not attached to the map) from the JVM side
+ */
+ HeatmapLayer::HeatmapLayer(jni::JNIEnv& env, jni::String layerId, jni::String sourceId)
+ : Layer(env, std::make_unique(jni::Make(env, layerId), jni::Make(env, sourceId))) {
+ }
+
+ /**
+ * Creates a non-owning peer object (for layers currently attached to the map)
+ */
+ HeatmapLayer::HeatmapLayer(mbgl::Map& map, mbgl::style::HeatmapLayer& coreLayer)
+ : Layer(map, coreLayer) {
+ }
+
+ /**
+ * Creates an owning peer object (for layers not attached to the map)
+ */
+ HeatmapLayer::HeatmapLayer(mbgl::Map& map, std::unique_ptr coreLayer)
+ : Layer(map, std::move(coreLayer)) {
+ }
+
+ HeatmapLayer::~HeatmapLayer() = default;
+
+ // Property getters
+
+ jni::Object HeatmapLayer::getHeatmapRadius(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapRadius());
+ return jni::Object(*converted);
+ }
+
+ jni::Object HeatmapLayer::getHeatmapRadiusTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapRadiusTransition();
+ return *convert>(env, options);
+ }
+
+ void HeatmapLayer::setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as()->HeatmapLayer::setHeatmapRadiusTransition(options);
+ }
+
+ jni::Object HeatmapLayer::getHeatmapWeight(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapWeight());
+ return jni::Object(*converted);
+ }
+
+ jni::Object HeatmapLayer::getHeatmapIntensity(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapIntensity());
+ return jni::Object(*converted);
+ }
+
+ jni::Object HeatmapLayer::getHeatmapIntensityTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapIntensityTransition();
+ return *convert>(env, options);
+ }
+
+ void HeatmapLayer::setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as()->HeatmapLayer::setHeatmapIntensityTransition(options);
+ }
+
+ jni::Object HeatmapLayer::getHeatmapOpacity(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapOpacity());
+ return jni::Object(*converted);
+ }
+
+ jni::Object HeatmapLayer::getHeatmapOpacityTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapOpacityTransition();
+ return *convert>(env, options);
+ }
+
+ void HeatmapLayer::setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as()->HeatmapLayer::setHeatmapOpacityTransition(options);
+ }
+
+
+ jni::Class HeatmapLayer::javaClass;
+
+ jni::jobject* HeatmapLayer::createJavaPeer(jni::JNIEnv& env) {
+ static auto constructor = HeatmapLayer::javaClass.template GetConstructor(env);
+ return HeatmapLayer::javaClass.New(env, constructor, reinterpret_cast(this));
+ }
+
+ void HeatmapLayer::registerNative(jni::JNIEnv& env) {
+ // Lookup the class
+ HeatmapLayer::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release();
+
+ #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name)
+
+ // Register the peer
+ jni::RegisterNativePeer(
+ env, HeatmapLayer::javaClass, "nativePtr",
+ std::make_unique,
+ "initialize",
+ "finalize",
+ METHOD(&HeatmapLayer::getHeatmapRadiusTransition, "nativeGetHeatmapRadiusTransition"),
+ METHOD(&HeatmapLayer::setHeatmapRadiusTransition, "nativeSetHeatmapRadiusTransition"),
+ METHOD(&HeatmapLayer::getHeatmapRadius, "nativeGetHeatmapRadius"),
+ METHOD(&HeatmapLayer::getHeatmapWeight, "nativeGetHeatmapWeight"),
+ METHOD(&HeatmapLayer::getHeatmapIntensityTransition, "nativeGetHeatmapIntensityTransition"),
+ METHOD(&HeatmapLayer::setHeatmapIntensityTransition, "nativeSetHeatmapIntensityTransition"),
+ METHOD(&HeatmapLayer::getHeatmapIntensity, "nativeGetHeatmapIntensity"),
+ METHOD(&HeatmapLayer::getHeatmapOpacityTransition, "nativeGetHeatmapOpacityTransition"),
+ METHOD(&HeatmapLayer::setHeatmapOpacityTransition, "nativeSetHeatmapOpacityTransition"),
+ METHOD(&HeatmapLayer::getHeatmapOpacity, "nativeGetHeatmapOpacity"));
+ }
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/style/layers/heatmap_layer.hpp b/platform/android/src/style/layers/heatmap_layer.hpp
new file mode 100644
index 000000000..85f9f0292
--- /dev/null
+++ b/platform/android/src/style/layers/heatmap_layer.hpp
@@ -0,0 +1,50 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#pragma once
+
+#include "layer.hpp"
+#include "../transition_options.hpp"
+#include
+#include
+
+namespace mbgl {
+namespace android {
+
+class HeatmapLayer : public Layer {
+public:
+
+ static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/layers/HeatmapLayer"; };
+
+ static jni::Class javaClass;
+
+ static void registerNative(jni::JNIEnv&);
+
+ HeatmapLayer(jni::JNIEnv&, jni::String, jni::String);
+
+ HeatmapLayer(mbgl::Map&, mbgl::style::HeatmapLayer&);
+
+ HeatmapLayer(mbgl::Map&, std::unique_ptr);
+
+ ~HeatmapLayer();
+
+ // Properties
+
+ jni::Object getHeatmapRadius(jni::JNIEnv&);
+ void setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object getHeatmapRadiusTransition(jni::JNIEnv&);
+
+ jni::Object getHeatmapWeight(jni::JNIEnv&);
+
+ jni::Object getHeatmapIntensity(jni::JNIEnv&);
+ void setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object getHeatmapIntensityTransition(jni::JNIEnv&);
+
+ jni::Object getHeatmapOpacity(jni::JNIEnv&);
+ void setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object getHeatmapOpacityTransition(jni::JNIEnv&);
+ jni::jobject* createJavaPeer(jni::JNIEnv&);
+
+}; // class HeatmapLayer
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp
index da1550bdb..29530879a 100644
--- a/platform/android/src/style/layers/layer.cpp
+++ b/platform/android/src/style/layers/layer.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/platform/android/src/style/layers/layers.cpp b/platform/android/src/style/layers/layers.cpp
index 5d1d1bbcb..5df689b45 100644
--- a/platform/android/src/style/layers/layers.cpp
+++ b/platform/android/src/style/layers/layers.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -16,6 +17,7 @@
#include "custom_layer.hpp"
#include "fill_extrusion_layer.hpp"
#include "fill_layer.hpp"
+#include "heatmap_layer.hpp"
#include "hillshade_layer.hpp"
#include "line_layer.hpp"
#include "raster_layer.hpp"
@@ -32,6 +34,7 @@ template <> struct PeerType { using Type = android::Back
template <> struct PeerType { using Type = android::CircleLayer; };
template <> struct PeerType { using Type = android::FillExtrusionLayer; };
template <> struct PeerType { using Type = android::FillLayer; };
+template <> struct PeerType { using Type = android::HeatmapLayer; };
template <> struct PeerType { using Type = android::HillshadeLayer; };
template <> struct PeerType { using Type = android::LineLayer; };
template <> struct PeerType { using Type = android::RasterLayer; };
@@ -95,6 +98,7 @@ void registerNativeLayers(jni::JNIEnv& env) {
CustomLayer::registerNative(env);
FillExtrusionLayer::registerNative(env);
FillLayer::registerNative(env);
+ HeatmapLayer::registerNative(env);
HillshadeLayer::registerNative(env);
LineLayer::registerNative(env);
RasterLayer::registerNative(env);
diff --git a/platform/android/src/style/sources/custom_geometry_source.cpp b/platform/android/src/style/sources/custom_geometry_source.cpp
index a60b962e4..b38405a3b 100644
--- a/platform/android/src/style/sources/custom_geometry_source.cpp
+++ b/platform/android/src/style/sources/custom_geometry_source.cpp
@@ -18,7 +18,7 @@ namespace mbgl {
namespace android {
// This conversion is expected not to fail because it's used only in contexts where
- // the value was originally a GeoJsonOptions object on the Java side. If it fails
+ // the value was originally a CustomGeometrySourceOptions object on the Java side. If it fails
// to convert, it's a bug in our serialization or Java-side static typing.
static style::CustomGeometrySource::Options convertCustomGeometrySourceOptions(jni::JNIEnv& env,
jni::Object<> options,
diff --git a/platform/darwin/docs/guides/For Style Authors.md.ejs b/platform/darwin/docs/guides/For Style Authors.md.ejs
index fc259e4da..37af150ac 100644
--- a/platform/darwin/docs/guides/For Style Authors.md.ejs
+++ b/platform/darwin/docs/guides/For Style Authors.md.ejs
@@ -160,6 +160,7 @@ the following terms for concepts defined in the style specification:
In the style specification | In the SDK
---------------------------|---------
+bounds | coordinate bounds
filter | predicate
function type | interpolation mode
id | identifier
@@ -200,6 +201,7 @@ In style JSON | In TileJSON | In the SDK
`tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]`
`minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel`
`maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel`
+`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds`
`tileSize` | — | `MGLTileSourceOptionTileSize`
`attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security)
`scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem`
diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js
index f919a649c..8e82a8eb6 100755
--- a/platform/darwin/scripts/generate-style-code.js
+++ b/platform/darwin/scripts/generate-style-code.js
@@ -318,14 +318,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;
@@ -397,7 +398,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 12cfa3157..b0c50a06f 100644
--- a/platform/darwin/scripts/style-spec-overrides-v8.json
+++ b/platform/darwin/scripts/style-spec-overrides-v8.json
@@ -23,6 +23,9 @@
"circle": {
"doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use many-sided regular polygons and configure their appearance using an `MGLFillStyleLayer` object."
},
+ "heatmap": {
+ "doc": "An `MGLHeatmapStyleLayer` is a style layer that renders a heatmap .\n\nA heatmap visualizes the spatial distribution of a large, dense set of point data, using color to avoid cluttering the map with individual points at low zoom levels. The points are weighted by an attribute you specify. Use a heatmap style layer in conjunction with point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nConsider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can alternatively cluster the source using the `MGLShapeSourceOptionClustered` option and render the data using an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer`."
+ },
"raster": {
"doc": "An `MGLRasterStyleLayer` is a style layer that renders georeferenced raster imagery on the map, especially raster tiles.\n\nUse a raster style layer to configure the color parameters of raster tiles loaded by an `MGLRasterSource` object or raster images loaded by an `MGLImageSource` object. For example, you could use a raster style layer to render Mapbox Satellite imagery, a raster tile set uploaded to Mapbox Studio, or a raster map authored in TileMill , the classic Mapbox Editor, or Mapbox Studio Classic.\n\nRaster images may also be used as icons or patterns in a style layer. To register an image for use as an icon or pattern, use the `-[MGLStyle setImage:forName:]` method. To configure a point annotation’s image, use the `MGLAnnotationImage` class."
},
@@ -79,6 +82,11 @@
"doc": "The base color of this layer. The extrusion's surfaces will be shaded differently based on this color in combination with the `light` settings. If this color is specified with an alpha component, the alpha component will be ignored; use `fill-extrusion-opacity` to set layer opacityco."
}
},
+ "paint_heatmap": {
+ "heatmap-color": {
+ "doc": "Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `$heatmapDensity` as input."
+ }
+ },
"paint_line": {
"line-pattern": {
"doc": "Name of image in style images to use for drawing image lines. For seamless patterns, image width must be a factor of two (2, 4, 8, ..., 512)."
@@ -114,4 +122,4 @@
"doc": "Distance that the text's anchor is moved from its original placement."
}
}
-}
\ No newline at end of file
+}
diff --git a/platform/darwin/src/MGLAbstractShapeSource.h b/platform/darwin/src/MGLAbstractShapeSource.h
index 3b35986b3..d61f41fbf 100644
--- a/platform/darwin/src/MGLAbstractShapeSource.h
+++ b/platform/darwin/src/MGLAbstractShapeSource.h
@@ -81,6 +81,31 @@ extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionBuffer;
*/
extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance;
+/**
+ An `NSNumber` object containing a Boolean value; specifies whether the shape of
+ an `MGLComputedShapeSource` should be wrapped to accomodate coordinates with
+ longitudes beyond −180 and 180. The default value is `NO`.
+
+ Setting this option to `YES` affects rendering performance.
+
+ This option is ignored when creating an instance of a class besides
+ `MGLComputedShapeSource`.
+ */
+extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionWrapsCoordinates;
+
+/**
+ An `NSNumber` object containing a Boolean value; specifies whether the shape of
+ an `MGLComputedShapeSource` should be clipped at the edge of each tile. The
+ default value is `NO`.
+
+ Setting this option to `YES` affects rendering performance. Use this option to
+ clip `MGLPolyline`s and `MGLPolygon`s at tile boundaries without artifacts.
+
+ This option is ignored when creating an instance of a class besides
+ `MGLComputedShapeSource`.
+ */
+extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClipsCoordinates;
+
/**
`MGLAbstractShapeSource` is an abstract base class for map content sources that
supply vector shapes to be shown on the map. A shape source is added to an
diff --git a/platform/darwin/src/MGLAbstractShapeSource.mm b/platform/darwin/src/MGLAbstractShapeSource.mm
index 755d04038..eb0807cee 100644
--- a/platform/darwin/src/MGLAbstractShapeSource.mm
+++ b/platform/darwin/src/MGLAbstractShapeSource.mm
@@ -8,6 +8,8 @@ const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSour
const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering";
const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel = @"MGLShapeSourceOptionMinimumZoomLevel";
const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance";
+const MGLShapeSourceOption MGLShapeSourceOptionWrapsCoordinates = @"MGLShapeSourceOptionWrapsCoordinates";
+const MGLShapeSourceOption MGLShapeSourceOptionClipsCoordinates = @"MGLShapeSourceOptionClipsCoordinates";
@interface MGLAbstractShapeSource ()
@@ -113,5 +115,22 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi
}
sourceOptions.tileOptions.tolerance = value.doubleValue;
}
+
+ if (NSNumber *value = options[MGLShapeSourceOptionWrapsCoordinates]) {
+ if (![value isKindOfClass:[NSNumber class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionWrapsCoordinates must be an NSNumber."];
+ }
+ sourceOptions.tileOptions.wrap = value.boolValue;
+ }
+
+ if (NSNumber *value = options[MGLShapeSourceOptionClipsCoordinates]) {
+ if (![value isKindOfClass:[NSNumber class]]) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLShapeSourceOptionClipsCoordinates must be an NSNumber."];
+ }
+ sourceOptions.tileOptions.clip = value.boolValue;
+ }
+
return sourceOptions;
}
diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.h b/platform/darwin/src/MGLHeatmapStyleLayer.h
new file mode 100644
index 000000000..35095fd52
--- /dev/null
+++ b/platform/darwin/src/MGLHeatmapStyleLayer.h
@@ -0,0 +1,189 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLFoundation.h"
+#import "MGLVectorStyleLayer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ An `MGLHeatmapStyleLayer` is a style layer that renders a heatmap .
+
+ A heatmap visualizes the spatial distribution of a large, dense set of point
+ data, using color to avoid cluttering the map with individual points at low
+ zoom levels. The points are weighted by an attribute you specify. Use a heatmap
+ style layer in conjunction with point or point collection features in vector
+ tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`,
+ `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature`
+ instances in an `MGLShapeSource` object.
+
+ Consider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or
+ `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point
+ data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can
+ alternatively cluster the source using the `MGLShapeSourceOptionClustered`
+ option and render the data using an `MGLCircleStyleLayer` or
+ `MGLSymbolStyleLayer`.
+
+ You can access an existing heatmap style layer using the
+ `-[MGLStyle layerWithIdentifier:]` method if you know its identifier;
+ otherwise, find it using the `MGLStyle.layers` property. You can also create a
+ new heatmap style layer and add it to the style using a method such as
+ `-[MGLStyle addLayer:]`.
+
+ ### Example
+
+ ```swift
+ let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes)
+ layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 0,
+ 6: 1])
+ layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)",
+ [0: 1,
+ 9: 3])
+ mapView.style?.addLayer(layer)
+ ```
+ */
+MGL_EXPORT
+@interface MGLHeatmapStyleLayer : MGLVectorStyleLayer
+
+/**
+ Returns a heatmap style layer initialized with an identifier and source.
+
+ After initializing and configuring the style layer, add it to a map view’s
+ style using the `-[MGLStyle addLayer:]` or
+ `-[MGLStyle insertLayer:belowLayer:]` method.
+
+ @param identifier A string that uniquely identifies the source in the style to
+ which it is added.
+ @param source The source from which to obtain the data to style. If the source
+ has not yet been added to the current style, the behavior is undefined.
+ @return An initialized foreground style layer.
+ */
+- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source;
+
+#pragma mark - Accessing the Paint Attributes
+
+/**
+ Defines the color of each point based on its density value in a heatmap. Should
+ be an expression that uses `$heatmapDensity` as input.
+
+ The default value of this property is an expression that evaluates to a rainbow
+ color scale from blue to red. Set this property to `nil` to reset it to the
+ default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant `UIColor` values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$heatmapDensity` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapColor;
+
+/**
+ Similar to `heatmapWeight` but controls the intensity of the heatmap globally.
+ Primarily used for adjusting the heatmap based on zoom level.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapIntensity;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapIntensity` property.
+
+ This property corresponds to the `heatmap-intensity-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapIntensityTransition;
+
+/**
+ The global opacity at which the heatmap layer will be drawn.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable
+
+ This property does not support applying interpolation or step functions to
+ feature attributes.
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapOpacity;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapOpacity` property.
+
+ This property corresponds to the `heatmap-opacity-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapOpacityTransition;
+
+/**
+ Radius of influence of one heatmap point in points. Increasing the value makes
+ the heatmap smoother, but less detailed.
+
+ This property is measured in points.
+
+ The default value of this property is an expression that evaluates to the float
+ `30`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable and/or
+ feature attributes
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapRadius;
+
+/**
+ The transition affecting any changes to this layer’s `heatmapRadius` property.
+
+ This property corresponds to the `heatmap-radius-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition heatmapRadiusTransition;
+
+/**
+ A measure of how much an individual point contributes to the heatmap. A value
+ of 10 would be equivalent to having 10 points of weight 1 in the same spot.
+ Especially useful when combined with clustering.
+
+ The default value of this property is an expression that evaluates to the float
+ `1`. Set this property to `nil` to reset it to the default value.
+
+ You can set this property to an expression containing any of the following:
+
+ * Constant numeric values
+ * Predefined functions, including mathematical and string operators
+ * Conditional expressions
+ * Variable assignments and references to assigned variables
+ * Interpolation and step functions applied to the `$zoomLevel` variable and/or
+ feature attributes
+ */
+@property (nonatomic, null_resettable) NSExpression *heatmapWeight;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.mm b/platform/darwin/src/MGLHeatmapStyleLayer.mm
new file mode 100644
index 000000000..a394dbda3
--- /dev/null
+++ b/platform/darwin/src/MGLHeatmapStyleLayer.mm
@@ -0,0 +1,210 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLSource.h"
+#import "NSPredicate+MGLAdditions.h"
+#import "NSDate+MGLAdditions.h"
+#import "MGLStyleLayer_Private.h"
+#import "MGLStyleValue_Private.h"
+#import "MGLHeatmapStyleLayer.h"
+
+#include
+#include
+
+@interface MGLHeatmapStyleLayer ()
+
+@property (nonatomic, readonly) mbgl::style::HeatmapLayer *rawLayer;
+
+@end
+
+@implementation MGLHeatmapStyleLayer
+
+- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source
+{
+ auto layer = std::make_unique(identifier.UTF8String, source.identifier.UTF8String);
+ return self = [super initWithPendingLayer:std::move(layer)];
+}
+
+- (mbgl::style::HeatmapLayer *)rawLayer
+{
+ return (mbgl::style::HeatmapLayer *)super.rawLayer;
+}
+
+- (NSString *)sourceIdentifier
+{
+ MGLAssertStyleLayerIsValid();
+
+ return @(self.rawLayer->getSourceID().c_str());
+}
+
+- (NSString *)sourceLayerIdentifier
+{
+ MGLAssertStyleLayerIsValid();
+
+ auto layerID = self.rawLayer->getSourceLayer();
+ return layerID.empty() ? nil : @(layerID.c_str());
+}
+
+- (void)setSourceLayerIdentifier:(NSString *)sourceLayerIdentifier
+{
+ MGLAssertStyleLayerIsValid();
+
+ self.rawLayer->setSourceLayer(sourceLayerIdentifier.UTF8String ?: "");
+}
+
+- (void)setPredicate:(NSPredicate *)predicate
+{
+ MGLAssertStyleLayerIsValid();
+
+ self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter());
+}
+
+- (NSPredicate *)predicate
+{
+ MGLAssertStyleLayerIsValid();
+
+ return [NSPredicate mgl_predicateWithFilter:self.rawLayer->getFilter()];
+}
+
+#pragma mark - Accessing the Paint Attributes
+
+- (void)setHeatmapColor:(NSExpression *)heatmapColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer().toPropertyValue(heatmapColor);
+ self.rawLayer->setHeatmapColor(mbglValue);
+}
+
+- (NSExpression *)heatmapColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapColor();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapColor();
+ }
+ return MGLStyleValueTransformer().toExpression(propertyValue);
+}
+
+- (void)setHeatmapIntensity:(NSExpression *)heatmapIntensity {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapIntensity);
+ self.rawLayer->setHeatmapIntensity(mbglValue);
+}
+
+- (NSExpression *)heatmapIntensity {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapIntensity();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapIntensity();
+ }
+ return MGLStyleValueTransformer().toExpression(propertyValue);
+}
+
+- (void)setHeatmapIntensityTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHeatmapIntensityTransition(options);
+}
+
+- (MGLTransition)heatmapIntensityTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapIntensityTransition();
+ MGLTransition transition;
+ transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero()));
+ transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero()));
+
+ return transition;
+}
+
+- (void)setHeatmapOpacity:(NSExpression *)heatmapOpacity {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapOpacity);
+ self.rawLayer->setHeatmapOpacity(mbglValue);
+}
+
+- (NSExpression *)heatmapOpacity {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapOpacity();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapOpacity();
+ }
+ return MGLStyleValueTransformer().toExpression(propertyValue);
+}
+
+- (void)setHeatmapOpacityTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHeatmapOpacityTransition(options);
+}
+
+- (MGLTransition)heatmapOpacityTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapOpacityTransition();
+ MGLTransition transition;
+ transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero()));
+ transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero()));
+
+ return transition;
+}
+
+- (void)setHeatmapRadius:(NSExpression *)heatmapRadius {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapRadius);
+ self.rawLayer->setHeatmapRadius(mbglValue);
+}
+
+- (NSExpression *)heatmapRadius {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapRadius();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapRadius();
+ }
+ return MGLStyleValueTransformer().toExpression(propertyValue);
+}
+
+- (void)setHeatmapRadiusTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHeatmapRadiusTransition(options);
+}
+
+- (MGLTransition)heatmapRadiusTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapRadiusTransition();
+ MGLTransition transition;
+ transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero()));
+ transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero()));
+
+ return transition;
+}
+
+- (void)setHeatmapWeight:(NSExpression *)heatmapWeight {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapWeight);
+ self.rawLayer->setHeatmapWeight(mbglValue);
+}
+
+- (NSExpression *)heatmapWeight {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHeatmapWeight();
+ if (propertyValue.isUndefined()) {
+ propertyValue = self.rawLayer->getDefaultHeatmapWeight();
+ }
+ return MGLStyleValueTransformer().toExpression(propertyValue);
+}
+
+@end
diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm
index f03bd70c8..11a544276 100644
--- a/platform/darwin/src/MGLMapSnapshotter.mm
+++ b/platform/darwin/src/MGLMapSnapshotter.mm
@@ -81,7 +81,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
@end
@interface MGLMapSnapshotter()
-
+@property (nonatomic) BOOL loading;
@end
@implementation MGLMapSnapshotter {
@@ -90,6 +90,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
std::unique_ptr _mbglMapSnapshotter;
std::unique_ptr> _snapshotCallback;
NS_ARRAY_OF(MGLAttributionInfo *) *_attributionInfo;
+
}
- (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options
@@ -115,182 +116,192 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64;
format:@"Already started this snapshotter."];
}
- _loading = true;
+ self.loading = true;
- dispatch_async(queue, ^{
- _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) {
- _loading = false;
+ __weak __typeof__(self) weakSelf = self;
+ _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) {
+ __typeof__(self) strongSelf = weakSelf;
+ strongSelf.loading = false;
+
+
+ if (mbglError) {
+ NSString *description = @(mbgl::util::toString(mbglError).c_str());
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
+ NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo];
- NSMutableArray *infos = [NSMutableArray array];
-
+ // Dispatch result to origin queue
+ dispatch_async(queue, ^{
+ completion(nil, error);
+ });
+ } else {
#if TARGET_OS_IPHONE
- CGFloat fontSize = [UIFont smallSystemFontSize];
- UIColor *attributeFontColor = [UIColor blackColor];
+ MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:strongSelf.options.scale];
#else
- CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize];
- NSColor *attributeFontColor = [NSColor blackColor];
+ MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)];
+ mglImage.size = NSMakeSize(mglImage.size.width / strongSelf.options.scale,
+ mglImage.size.height / strongSelf.options.scale);
#endif
- for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) {
- NSString *attributionHTMLString = @(attribution->c_str());
- NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString
- fontSize:fontSize
- linkColor:attributeFontColor];
- [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos];
- }
-
- _attributionInfo = infos;
-
- if (mbglError) {
- NSString *description = @(mbgl::util::toString(mbglError).c_str());
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description};
- NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo];
-
- // Dispatch result to origin queue
- dispatch_async(queue, ^{
- completion(nil, error);
- });
- } else {
-#if TARGET_OS_IPHONE
- MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:self.options.scale];
-#else
- MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)];
- mglImage.size = NSMakeSize(mglImage.size.width / self.options.scale,
- mglImage.size.height / self.options.scale);
-#endif
-
- // Process image watermark in a work queue
- dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_async(workQueue, ^{
-#if TARGET_OS_IPHONE
- MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
- for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
- attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
- CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
- if (attributionSize.width <= mglImage.size.width) {
- break;
- }
- }
-
- UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
- CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
-
- CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height);
- CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width,
- logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
- if (!logoImage) {
- CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
- logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height);
- attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
- }
-
- CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
- attributionOrigin.y,
- attributionBackgroundSize.width,
- attributionBackgroundSize.height);
- CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
- attributionBackgroundFrame.origin.y - 1);
-
- CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale,
- attributionBackgroundFrame.origin.y * mglImage.scale,
- attributionBackgroundSize.width * mglImage.scale,
- attributionBackgroundSize.height * mglImage.scale);
-
-
- UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale);
-
- [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)];
-
- [logoImage drawInRect:logoImageRect];
-
- UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext();
- CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect);
- UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef];
- CGImageRelease(attributionImageRef);
-
- CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage];
-
- UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage];
-
- [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
-
- [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
-
- UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext();
-
- UIGraphicsEndImageContext();
-#else
- NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height);
- NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height);
-
- MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
- for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
- attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
- CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
- if (attributionSize.width <= mglImage.size.width) {
- break;
- }
- }
-
- NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
- CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
- NSImage *sourceImage = mglImage;
-
- CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height);
- CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width,
- MGLLogoImagePosition.y + 1);
- if (!logoImage) {
- CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
- logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height);
- attributionOrigin = CGPointMake(10, attributionOrigin.y);
- }
-
- CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
- attributionOrigin.y,
- attributionBackgroundSize.width,
- attributionBackgroundSize.height);
- CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
- logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2));
-
-
- NSImage *compositedImage = nil;
- NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame
- context:nil
- hints:nil];
- compositedImage = [[NSImage alloc] initWithSize:targetSize];
-
- [compositedImage lockFocus];
-
- [sourceImageRep drawInRect: targetFrame];
-
- if (logoImage) {
- [logoImage drawInRect:logoImageRect];
- }
-
- NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame];
-
- CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]];
-
- NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage];
-
- [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
-
- [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
-
- [compositedImage unlockFocus];
-
-
-#endif
-
- // Dispatch result to origin queue
- dispatch_async(queue, ^{
- MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn];
- completion(snapshot, nil);
- });
- });
- }
- });
- _mbglMapSnapshotter->snapshot(_snapshotCallback->self());
+ [strongSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn queue:queue completionHandler:completion];
+ }
+ _snapshotCallback = NULL;
});
+ dispatch_async(queue, ^{
+ _mbglMapSnapshotter->snapshot(_snapshotCallback->self());
+
+ });
+}
+
+- (MGLImage *)drawAttributedSnapshot:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn queue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion {
+
+ NSMutableArray *infos = [NSMutableArray array];
+
+#if TARGET_OS_IPHONE
+ CGFloat fontSize = [UIFont smallSystemFontSize];
+ UIColor *attributeFontColor = [UIColor blackColor];
+#else
+ CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize];
+ NSColor *attributeFontColor = [NSColor blackColor];
+#endif
+ for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) {
+ NSString *attributionHTMLString = @(attribution->c_str());
+ NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString
+ fontSize:fontSize
+ linkColor:attributeFontColor];
+ [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos];
+ }
+
+ _attributionInfo = infos;
+
+ // Process image watermark in a work queue
+ dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(workQueue, ^{
+#if TARGET_OS_IPHONE
+ MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
+ for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
+ attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
+ CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
+ if (attributionSize.width <= mglImage.size.width) {
+ break;
+ }
+ }
+
+ UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
+ CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
+
+ CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height);
+ CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width,
+ logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
+ if (!logoImage) {
+ CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
+ logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height);
+ attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1);
+ }
+
+ CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
+ attributionOrigin.y,
+ attributionBackgroundSize.width,
+ attributionBackgroundSize.height);
+ CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
+ attributionBackgroundFrame.origin.y - 1);
+
+ CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale,
+ attributionBackgroundFrame.origin.y * mglImage.scale,
+ attributionBackgroundSize.width * mglImage.scale,
+ attributionBackgroundSize.height * mglImage.scale);
+
+
+ UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale);
+
+ [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)];
+
+ [logoImage drawInRect:logoImageRect];
+
+ UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext();
+ CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect);
+ UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef];
+ CGImageRelease(attributionImageRef);
+
+ CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage];
+
+ UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage];
+
+ [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
+
+ [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
+
+ UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext();
+
+ UIGraphicsEndImageContext();
+#else
+ NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height);
+ NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height);
+
+ MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong;
+ for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) {
+ attributionInfoStyle = (MGLAttributionInfoStyle)styleValue;
+ CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle];
+ if (attributionSize.width <= mglImage.size.width) {
+ break;
+ }
+ }
+
+ NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle];
+ CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle];
+ NSImage *sourceImage = mglImage;
+
+ CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height);
+ CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width,
+ MGLLogoImagePosition.y + 1);
+ if (!logoImage) {
+ CGSize defaultLogoSize = [self mapboxLongStyleLogo].size;
+ logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height);
+ attributionOrigin = CGPointMake(10, attributionOrigin.y);
+ }
+
+ CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x,
+ attributionOrigin.y,
+ attributionBackgroundSize.width,
+ attributionBackgroundSize.height);
+ CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10,
+ logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2));
+
+
+ NSImage *compositedImage = nil;
+ NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame
+ context:nil
+ hints:nil];
+ compositedImage = [[NSImage alloc] initWithSize:targetSize];
+
+ [compositedImage lockFocus];
+
+ [sourceImageRep drawInRect: targetFrame];
+
+ if (logoImage) {
+ [logoImage drawInRect:logoImageRect];
+ }
+
+ NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame];
+
+ CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]];
+
+ NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage];
+
+ [blurredAttributionBackground drawInRect:attributionBackgroundFrame];
+
+ [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition];
+
+ [compositedImage unlockFocus];
+
+
+#endif
+
+ // Dispatch result to origin queue
+ dispatch_async(queue, ^{
+ MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn];
+ completion(snapshot, nil);
+ });
+ });
+ return nil;
}
- (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin
diff --git a/platform/darwin/src/MGLRendererFrontend.h b/platform/darwin/src/MGLRendererFrontend.h
index 76904d008..2df67ca4e 100644
--- a/platform/darwin/src/MGLRendererFrontend.h
+++ b/platform/darwin/src/MGLRendererFrontend.h
@@ -56,9 +56,9 @@ public:
return renderer.get();
}
- void onLowMemory() {
+ void reduceMemoryUse() {
if (!renderer) return;
- renderer->onLowMemory();
+ renderer->reduceMemoryUse();
}
private:
diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm
index 512894431..f6fc5533b 100644
--- a/platform/darwin/src/MGLStyle.mm
+++ b/platform/darwin/src/MGLStyle.mm
@@ -8,6 +8,7 @@
#import "MGLLineStyleLayer.h"
#import "MGLCircleStyleLayer.h"
#import "MGLSymbolStyleLayer.h"
+#import "MGLHeatmapStyleLayer.h"
#import "MGLHillshadeStyleLayer.h"
#import "MGLRasterStyleLayer.h"
#import "MGLBackgroundStyleLayer.h"
@@ -36,6 +37,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -401,6 +403,8 @@ static NSURL *MGLStyleURL_trafficNight;
return [[MGLSymbolStyleLayer alloc] initWithRawLayer:symbolLayer];
} else if (auto rasterLayer = rawLayer->as()) {
return [[MGLRasterStyleLayer alloc] initWithRawLayer:rasterLayer];
+ } else if (auto heatmapLayer = rawLayer->as()) {
+ return [[MGLHeatmapStyleLayer alloc] initWithRawLayer:heatmapLayer];
} else if (auto hillshadeLayer = rawLayer->as()) {
return [[MGLHillshadeStyleLayer alloc] initWithRawLayer:hillshadeLayer];
} else if (auto circleLayer = rawLayer->as()) {
diff --git a/platform/darwin/src/MGLStyleLayer.mm.ejs b/platform/darwin/src/MGLStyleLayer.mm.ejs
index 41b029791..ac7676a1c 100644
--- a/platform/darwin/src/MGLStyleLayer.mm.ejs
+++ b/platform/darwin/src/MGLStyleLayer.mm.ejs
@@ -157,7 +157,9 @@ namespace mbgl {
- (void)set<%- camelize(property.name) %>:(NSExpression *)<%- objCName(property) %> {
MGLAssertStyleLayerIsValid();
-<% if (property["property-function"]) { -%>
+<% if (property.name === 'heatmap-color') { -%>
+ auto mbglValue = MGLStyleValueTransformer().toPropertyValue(heatmapColor);
+<% } else if (property["property-function"]) { -%>
auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue>>(<%- objCName(property) %>);
<% } else { -%>
auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue>>(<%- objCName(property) %>);
diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h
index ba4b413a3..5124c29a9 100644
--- a/platform/darwin/src/MGLStyleValue_Private.h
+++ b/platform/darwin/src/MGLStyleValue_Private.h
@@ -10,6 +10,7 @@
#import "MGLConversion.h"
#include
#include
+#include
#include
#import
@@ -52,11 +53,20 @@ public:
return mbglValue.evaluate(evaluator);
}
+ // Convert an mbgl heatmap color property value into an mgl style value
+ NSExpression *toExpression(const mbgl::style::HeatmapColorPropertyValue &mbglValue) {
+ if (mbglValue.isUndefined()) {
+ return nil;
+ }
+ return [NSExpression mgl_expressionWithJSONObject:MGLJSONObjectFromMBGLExpression(mbglValue.getExpression())];
+ }
+
/**
Converts an NSExpression to an mbgl property value.
*/
template
- MBGLValue toPropertyValue(NSExpression *expression) {
+ typename std::enable_if_t::value,
+ MBGLValue> toPropertyValue(NSExpression *expression) {
if (!expression) {
return {};
}
@@ -85,6 +95,30 @@ public:
return *value;
}
+
+ /**
+ Converts an NSExpression to an mbgl property value.
+ */
+ template
+ typename std::enable_if_t::value,
+ MBGLValue> toPropertyValue(NSExpression *expression) {
+ if (!expression) {
+ return {};
+ }
+
+ NSArray *jsonExpression = expression.mgl_jsonExpressionObject;
+
+ mbgl::style::conversion::Error valueError;
+ auto value = mbgl::style::conversion::convert(
+ mbgl::style::conversion::makeConvertible(jsonExpression), valueError);
+ if (!value) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Invalid property value: %@", @(valueError.message.c_str())];
+ return {};
+ }
+
+ return *value;
+ }
private: // Private utilities for converting from mgl to mbgl values
diff --git a/platform/darwin/src/MGLTileSource.h b/platform/darwin/src/MGLTileSource.h
index caeafcd2f..3dc268db6 100644
--- a/platform/darwin/src/MGLTileSource.h
+++ b/platform/darwin/src/MGLTileSource.h
@@ -41,6 +41,20 @@ extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel;
*/
extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel;
+/**
+ An `NSValue` object containing an `MGLCoordinateBounds` struct that specifies
+ the geographic extent of the source.
+
+ If this option is specified, the SDK avoids requesting any tile that falls
+ outside of the coordinate bounds. Otherwise, the SDK requests any tile needed
+ to cover the viewport, as it does by default.
+
+ This option corresponds to the `bounds` key in the
+ TileJSON
+ specification.
+ */
+extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds;
+
#if TARGET_OS_IPHONE
/**
An HTML string defining the buttons to be displayed in an action sheet when the
diff --git a/platform/darwin/src/MGLTileSource.mm b/platform/darwin/src/MGLTileSource.mm
index 5644ad9a0..bc985bd72 100644
--- a/platform/darwin/src/MGLTileSource.mm
+++ b/platform/darwin/src/MGLTileSource.mm
@@ -1,7 +1,9 @@
#import "MGLTileSource_Private.h"
#import "MGLAttributionInfo_Private.h"
+#import "MGLGeometry_Private.h"
#import "NSString+MGLAdditions.h"
+#import "NSValue+MGLAdditions.h"
#if TARGET_OS_IPHONE
#import
@@ -13,6 +15,7 @@
const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel = @"MGLTileSourceOptionMinimumZoomLevel";
const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel = @"MGLTileSourceOptionMaximumZoomLevel";
+const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds = @"MGLTileSourceOptionCoordinateBounds";
const MGLTileSourceOption MGLTileSourceOptionAttributionHTMLString = @"MGLTileSourceOptionAttributionHTMLString";
const MGLTileSourceOption MGLTileSourceOptionAttributionInfos = @"MGLTileSourceOptionAttributionInfos";
const MGLTileSourceOption MGLTileSourceOptionTileCoordinateSystem = @"MGLTileSourceOptionTileCoordinateSystem";
@@ -70,6 +73,15 @@ mbgl::Tileset MGLTileSetFromTileURLTemplates(NS_ARRAY_OF(NSString *) *tileURLTem
[NSException raise:NSInvalidArgumentException
format:@"MGLTileSourceOptionMinimumZoomLevel must be less than MGLTileSourceOptionMaximumZoomLevel."];
}
+
+ if (NSValue *coordinateBounds = options[MGLTileSourceOptionCoordinateBounds]) {
+ if (![coordinateBounds isKindOfClass:[NSValue class]]
+ && strcmp(coordinateBounds.objCType, @encode(MGLCoordinateBounds)) == 0) {
+ [NSException raise:NSInvalidArgumentException
+ format:@"MGLTileSourceOptionCoordinateBounds must be set to an NSValue containing an MGLCoordinateBounds."];
+ }
+ tileSet.bounds = MGLLatLngBoundsFromCoordinateBounds(coordinateBounds.MGLCoordinateBoundsValue);
+ }
if (NSString *attribution = options[MGLTileSourceOptionAttributionHTMLString]) {
if (![attribution isKindOfClass:[NSString class]]) {
diff --git a/platform/darwin/src/MGLVectorStyleLayer.h b/platform/darwin/src/MGLVectorStyleLayer.h
index 7780a34c7..177b1b70f 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 9bf992486..5a6e00bc4 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 000000000..8d44064d9
--- /dev/null
+++ b/platform/darwin/test/MGLHeatmapColorTests.mm
@@ -0,0 +1,61 @@
+#import
+#import
+
+#import "MGLStyleLayer_Private.h"
+
+#include
+
+@interface MGLHeatmapColorTests : XCTestCase
+@end
+
+@implementation MGLHeatmapColorTests
+
+- (void)testProperties {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+ MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+
+ auto rawLayer = layer.rawLayer->as();
+
+ XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
+ @"heatmap-color should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapColor;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]];
+ layer.heatmapColor = constantExpression;
+
+
+ mbgl::style::PropertyValue propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(),
+ @"Setting heatmapColor to a constant value expression should update heatmap-color.");
+ XCTAssertEqualObjects(layer.heatmapColor, constantExpression,
+ @"heatmapColor should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]];
+ NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MGLColor blueColor]];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($heatmapDensity, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@12: constantExpression2}];
+ layer.heatmapColor = functionExpression;
+
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(),
+ @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
+ XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(),
+ @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color.");
+ XCTAssertEqualObjects(layer.heatmapColor, functionExpression,
+ @"heatmapColor should round-trip expressions depending on $heatmapDensity.");
+
+ layer.heatmapColor = nil;
+ XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(),
+ @"Unsetting heatmapColor should return heatmap-color to the default value.");
+ XCTAssertEqualObjects(layer.heatmapColor, defaultExpression,
+ @"heatmapColor should return the default value after being unset.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor.");
+ functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a data expression is applied to heatmapColor.");
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+}
+
+@end
diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm
new file mode 100644
index 000000000..74121affd
--- /dev/null
+++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm
@@ -0,0 +1,296 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLStyleLayerTests.h"
+#import "../../darwin/src/NSDate+MGLAdditions.h"
+
+#import "MGLStyleLayer_Private.h"
+
+#include
+#include
+
+@interface MGLHeatmapLayerTests : MGLStyleLayerTests
+@end
+
+@implementation MGLHeatmapLayerTests
+
++ (NSString *)layerType {
+ return @"heatmap";
+}
+
+- (void)testPredicates {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+ MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+
+ XCTAssertNil(layer.sourceLayerIdentifier);
+ layer.sourceLayerIdentifier = @"layerID";
+ XCTAssertEqualObjects(layer.sourceLayerIdentifier, @"layerID");
+ layer.sourceLayerIdentifier = nil;
+ XCTAssertNil(layer.sourceLayerIdentifier);
+
+ XCTAssertNil(layer.predicate);
+ layer.predicate = [NSPredicate predicateWithValue:NO];
+ XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]);
+ layer.predicate = nil;
+ XCTAssertNil(layer.predicate);
+}
+
+- (void)testProperties {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+
+ MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+ XCTAssertNotEqual(layer.rawLayer, nullptr);
+ XCTAssertTrue(layer.rawLayer->is());
+ auto rawLayer = layer.rawLayer->as();
+
+ MGLTransition transitionTest = MGLTransitionMake(5, 4);
+
+
+ // heatmap-intensity
+ {
+ XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(),
+ @"heatmap-intensity should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapIntensity;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapIntensity = constantExpression;
+ mbgl::style::PropertyValue propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue,
+ @"Setting heatmapIntensity to a constant value expression should update heatmap-intensity.");
+ XCTAssertEqualObjects(layer.heatmapIntensity, constantExpression,
+ @"heatmapIntensity should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapIntensity = functionExpression;
+
+ mbgl::style::IntervalStops intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue,
+ @"Setting heatmapIntensity to a camera expression should update heatmap-intensity.");
+ XCTAssertEqualObjects(layer.heatmapIntensity, functionExpression,
+ @"heatmapIntensity should round-trip camera expressions.");
+
+
+
+ layer.heatmapIntensity = nil;
+ XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(),
+ @"Unsetting heatmapIntensity should return heatmap-intensity to the default value.");
+ XCTAssertEqualObjects(layer.heatmapIntensity, defaultExpression,
+ @"heatmapIntensity should return the default value after being unset.");
+
+ functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
+ XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ // Transition property test
+ layer.heatmapIntensityTransition = transitionTest;
+ auto toptions = rawLayer->getHeatmapIntensityTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition heatmapIntensityTransition = layer.heatmapIntensityTransition;
+ XCTAssertEqual(heatmapIntensityTransition.delay, transitionTest.delay);
+ XCTAssertEqual(heatmapIntensityTransition.duration, transitionTest.duration);
+ }
+
+ // heatmap-opacity
+ {
+ XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(),
+ @"heatmap-opacity should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapOpacity;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapOpacity = constantExpression;
+ mbgl::style::PropertyValue propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue,
+ @"Setting heatmapOpacity to a constant value expression should update heatmap-opacity.");
+ XCTAssertEqualObjects(layer.heatmapOpacity, constantExpression,
+ @"heatmapOpacity should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapOpacity = functionExpression;
+
+ mbgl::style::IntervalStops intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue,
+ @"Setting heatmapOpacity to a camera expression should update heatmap-opacity.");
+ XCTAssertEqualObjects(layer.heatmapOpacity, functionExpression,
+ @"heatmapOpacity should round-trip camera expressions.");
+
+
+
+ layer.heatmapOpacity = nil;
+ XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(),
+ @"Unsetting heatmapOpacity should return heatmap-opacity to the default value.");
+ XCTAssertEqualObjects(layer.heatmapOpacity, defaultExpression,
+ @"heatmapOpacity should return the default value after being unset.");
+
+ functionExpression = [NSExpression expressionForKeyPath:@"bogus"];
+ XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes.");
+ // Transition property test
+ layer.heatmapOpacityTransition = transitionTest;
+ auto toptions = rawLayer->getHeatmapOpacityTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition heatmapOpacityTransition = layer.heatmapOpacityTransition;
+ XCTAssertEqual(heatmapOpacityTransition.delay, transitionTest.delay);
+ XCTAssertEqual(heatmapOpacityTransition.duration, transitionTest.duration);
+ }
+
+ // heatmap-radius
+ {
+ XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(),
+ @"heatmap-radius should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapRadius;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapRadius = constantExpression;
+ mbgl::style::DataDrivenPropertyValue propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a constant value expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, constantExpression,
+ @"heatmapRadius should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapRadius = functionExpression;
+
+ mbgl::style::IntervalStops intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a camera expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, functionExpression,
+ @"heatmapRadius should round-trip camera expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}];
+ layer.heatmapRadius = functionExpression;
+
+ mbgl::style::ExponentialStops exponentialStops = { {{18, 0xff}}, 1.0 };
+ propertyValue = mbgl::style::SourceFunction { "keyName", exponentialStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a data expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, functionExpression,
+ @"heatmapRadius should round-trip data expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ layer.heatmapRadius = functionExpression;
+
+ std::map innerStops { {18, 0xff} };
+ mbgl::style::CompositeExponentialStops compositeStops { { {10.0, innerStops} }, 1.0 };
+
+ propertyValue = mbgl::style::CompositeFunction { "keyName", compositeStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue,
+ @"Setting heatmapRadius to a camera-data expression should update heatmap-radius.");
+ XCTAssertEqualObjects(layer.heatmapRadius, functionExpression,
+ @"heatmapRadius should round-trip camera-data expressions.");
+
+
+ layer.heatmapRadius = nil;
+ XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(),
+ @"Unsetting heatmapRadius should return heatmap-radius to the default value.");
+ XCTAssertEqualObjects(layer.heatmapRadius, defaultExpression,
+ @"heatmapRadius should return the default value after being unset.");
+ // Transition property test
+ layer.heatmapRadiusTransition = transitionTest;
+ auto toptions = rawLayer->getHeatmapRadiusTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition heatmapRadiusTransition = layer.heatmapRadiusTransition;
+ XCTAssertEqual(heatmapRadiusTransition.delay, transitionTest.delay);
+ XCTAssertEqual(heatmapRadiusTransition.duration, transitionTest.duration);
+ }
+
+ // heatmap-weight
+ {
+ XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(),
+ @"heatmap-weight should be unset initially.");
+ NSExpression *defaultExpression = layer.heatmapWeight;
+
+ NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ layer.heatmapWeight = constantExpression;
+ mbgl::style::DataDrivenPropertyValue propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a constant value expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, constantExpression,
+ @"heatmapWeight should round-trip constant value expressions.");
+
+ constantExpression = [NSExpression expressionWithFormat:@"0xff"];
+ NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}];
+ layer.heatmapWeight = functionExpression;
+
+ mbgl::style::IntervalStops intervalStops = {{
+ { -INFINITY, 0xff },
+ { 18, 0xff },
+ }};
+ propertyValue = mbgl::style::CameraFunction { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a camera expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, functionExpression,
+ @"heatmapWeight should round-trip camera expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}];
+ layer.heatmapWeight = functionExpression;
+
+ mbgl::style::ExponentialStops exponentialStops = { {{18, 0xff}}, 1.0 };
+ propertyValue = mbgl::style::SourceFunction { "keyName", exponentialStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a data expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, functionExpression,
+ @"heatmapWeight should round-trip data expressions.");
+
+ functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}];
+ layer.heatmapWeight = functionExpression;
+
+ std::map innerStops { {18, 0xff} };
+ mbgl::style::CompositeExponentialStops compositeStops { { {10.0, innerStops} }, 1.0 };
+
+ propertyValue = mbgl::style::CompositeFunction { "keyName", compositeStops };
+
+ XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue,
+ @"Setting heatmapWeight to a camera-data expression should update heatmap-weight.");
+ XCTAssertEqualObjects(layer.heatmapWeight, functionExpression,
+ @"heatmapWeight should round-trip camera-data expressions.");
+
+
+ layer.heatmapWeight = nil;
+ XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(),
+ @"Unsetting heatmapWeight should return heatmap-weight to the default value.");
+ XCTAssertEqualObjects(layer.heatmapWeight, defaultExpression,
+ @"heatmapWeight should return the default value after being unset.");
+ }
+}
+
+- (void)testPropertyNames {
+ [self testPropertyName:@"heatmap-intensity" isBoolean:NO];
+ [self testPropertyName:@"heatmap-opacity" isBoolean:NO];
+ [self testPropertyName:@"heatmap-radius" isBoolean:NO];
+ [self testPropertyName:@"heatmap-weight" isBoolean:NO];
+}
+
+@end
diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs
index e26c63e66..e17501ed1 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/darwin/test/MGLTileSetTests.mm b/platform/darwin/test/MGLTileSetTests.mm
index 40eab5f97..4d5e1fcd0 100644
--- a/platform/darwin/test/MGLTileSetTests.mm
+++ b/platform/darwin/test/MGLTileSetTests.mm
@@ -2,6 +2,7 @@
#import
#import "MGLTileSource_Private.h"
+#import "MGLGeometry_Private.h"
#include
@@ -40,6 +41,19 @@
XCTAssertEqual(tileSet.zoomRange.min, 1);
XCTAssertEqual(tileSet.zoomRange.max, 2);
+ // when the tile set has a bounds set
+ MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78));
+ tileSet = MGLTileSetFromTileURLTemplates(@[@"tile.1"], @{
+ MGLTileSourceOptionCoordinateBounds: @(bounds),
+ });
+
+ // the mbgl object reflects the set values for the bounds
+ XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds");
+ if (tileSet.bounds) {
+ MGLCoordinateBounds actual = MGLCoordinateBoundsFromLatLngBounds(*tileSet.bounds);
+ XCTAssert(MGLCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip");
+ }
+
// when the tile set has an attribution
NSString *attribution = @"my tileset © ©️🎈";
tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{
diff --git a/platform/default/asset_file_source.cpp b/platform/default/asset_file_source.cpp
index 54dbb8d0f..3063bf88a 100644
--- a/platform/default/asset_file_source.cpp
+++ b/platform/default/asset_file_source.cpp
@@ -10,6 +10,12 @@
#include
#include
+namespace {
+
+const std::string assetProtocol = "asset://";
+
+} // namespace
+
namespace mbgl {
class AssetFileSource::Impl {
@@ -19,18 +25,17 @@ public:
}
void request(const std::string& url, ActorRef req) {
- std::string path;
-
- if (url.size() <= 8 || url[8] == '/') {
- // This is an empty or absolute path.
- path = mbgl::util::percentDecode(url.substr(8));
- } else {
- // This is a relative path. Prefix with the application root.
- path = root + "/" + mbgl::util::percentDecode(url.substr(8));
- }
-
Response response;
+ if (!acceptsURL(url)) {
+ response.error = std::make_unique(Response::Error::Reason::Other,
+ "Invalid asset URL");
+ req.invoke(&FileSourceRequest::setResponse, response);
+ return;
+ }
+
+ // Cut off the protocol and prefix with path.
+ const auto path = root + "/" + mbgl::util::percentDecode(url.substr(assetProtocol.size()));
struct stat buf;
int result = stat(path.c_str(), &buf);
@@ -69,4 +74,8 @@ std::unique_ptr AssetFileSource::request(const Resource& resource,
return std::move(req);
}
+bool AssetFileSource::acceptsURL(const std::string& url) {
+ return std::equal(assetProtocol.begin(), assetProtocol.end(), url.begin());
+}
+
} // namespace mbgl
diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp
index 608b782ab..cb602995a 100644
--- a/platform/default/default_file_source.cpp
+++ b/platform/default/default_file_source.cpp
@@ -14,16 +14,6 @@
#include
-namespace {
-
-const std::string assetProtocol = "asset://";
-
-bool isAssetURL(const std::string& url) {
- return std::equal(assetProtocol.begin(), assetProtocol.end(), url.begin());
-}
-
-} // namespace
-
namespace mbgl {
class DefaultFileSource::Impl {
@@ -118,7 +108,7 @@ public:
ref.invoke(&FileSourceRequest::setResponse, res);
};
- if (isAssetURL(resource.url)) {
+ if (AssetFileSource::acceptsURL(resource.url)) {
//Asset request
tasks[req] = assetFileSource->request(resource, callback);
} else if (LocalFileSource::acceptsURL(resource.url)) {
diff --git a/platform/default/local_file_source.cpp b/platform/default/local_file_source.cpp
index 21803d093..0635e86d8 100644
--- a/platform/default/local_file_source.cpp
+++ b/platform/default/local_file_source.cpp
@@ -16,8 +16,7 @@
namespace {
-const char* protocol = "file://";
-const std::size_t protocolLength = 7;
+const std::string fileProtocol = "file://";
} // namespace
@@ -28,11 +27,17 @@ public:
Impl(ActorRef) {}
void request(const std::string& url, ActorRef req) {
- // Cut off the protocol
- std::string path = mbgl::util::percentDecode(url.substr(protocolLength));
-
Response response;
+ if (!acceptsURL(url)) {
+ response.error = std::make_unique(Response::Error::Reason::Other,
+ "Invalid file URL");
+ req.invoke(&FileSourceRequest::setResponse, response);
+ return;
+ }
+
+ // Cut off the protocol and prefix with path.
+ const auto path = mbgl::util::percentDecode(url.substr(fileProtocol.size()));
struct stat buf;
int result = stat(path.c_str(), &buf);
@@ -70,7 +75,7 @@ std::unique_ptr LocalFileSource::request(const Resource& resource,
}
bool LocalFileSource::acceptsURL(const std::string& url) {
- return url.compare(0, protocolLength, protocol) == 0;
+ return std::equal(fileProtocol.begin(), fileProtocol.end(), url.begin());
}
} // namespace mbgl
diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md
index 0cb39024a..75a732117 100644
--- a/platform/ios/CHANGELOG.md
+++ b/platform/ios/CHANGELOG.md
@@ -11,6 +11,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))
@@ -34,15 +35,19 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT
### Map snapshots
* Fixed a memory leak that occurred when creating a map snapshot. ([#10585](https://github.com/mapbox/mapbox-gl-native/pull/10585))
-* Fixed an issue that caused `MGLMapSnapshotter.pointForCoordinate` to return an incorrect value. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035))
-### Other changes
+## Other changes
* Feature querying results now account for the `MGLSymbolStyleLayer.circleStrokeWidth` property. ([#10897](https://github.com/mapbox/mapbox-gl-native/pull/10897))
* Fixed an issue preventing labels from being transliterated when VoiceOver was enabled on iOS 10._x_ and below. ([#10881](https://github.com/mapbox/mapbox-gl-native/pull/10881))
* Labels are now transliterated from more languages when VoiceOver is enabled. ([#10881](https://github.com/mapbox/mapbox-gl-native/pull/10881))
* Long-pressing the attribution button causes the SDK’s version number to be displayed in the action sheet that appears. ([#10650](https://github.com/mapbox/mapbox-gl-native/pull/10650))
+## 3.7.4 - February 12, 2018
+
+* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141))
+* Fixed an issue that caused `-[MGLMapSnapshotter pointForCoordinate:]` to return the wrong point. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035))
+
## 3.7.3 - January 10, 2018
* Fixed a crash while zooming while annotations are present on the map. ([#10791](https://github.com/mapbox/mapbox-gl-native/pull/10791))
diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m
index 3bf93d872..95d3251e2 100644
--- a/platform/ios/app/MBXSnapshotsViewController.m
+++ b/platform/ios/app/MBXSnapshotsViewController.m
@@ -50,12 +50,13 @@
options.zoomLevel = 10;
// Create and start the snapshotter
+ __weak UIImageView *weakImageView = imageView;
MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options];
[snapshotter startWithCompletionHandler: ^(MGLMapSnapshot* snapshot, NSError *error) {
if (error) {
NSLog(@"Could not load snapshot: %@", [error localizedDescription]);
} else {
- imageView.image = snapshot.image;
+ weakImageView.image = snapshot.image;
}
}];
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 0f617188b..282dc901d 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -1280,6 +1280,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
- (void)styleDynamicPointCollection
{
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) zoomLevel:14 animated:NO];
+
CLLocationCoordinate2D coordinates[] = {
{37.00145594210082, -109.04960632324219},
{37.00173012609867, -109.0404224395752},
@@ -1889,7 +1890,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
[self updateHUD];
}
-- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
+- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated
+{
[self updateHUD];
}
diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md
index 8db1bd331..02d23067e 100644
--- a/platform/ios/docs/guides/For Style Authors.md
+++ b/platform/ios/docs/guides/For Style Authors.md
@@ -109,6 +109,7 @@ the following terms for concepts defined in the style specification:
In the style specification | In the SDK
---------------------------|---------
+bounds | coordinate bounds
filter | predicate
function type | interpolation mode
id | identifier
@@ -149,6 +150,7 @@ In style JSON | In TileJSON | In the SDK
`tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]`
`minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel`
`maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel`
+`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds`
`tileSize` | — | `MGLTileSourceOptionTileSize`
`attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security)
`scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem`
@@ -190,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 68071123a..f66aa7496 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, ); }; };
@@ -297,6 +303,12 @@
96E5170320005A6800A02306 /* FABKitProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848811CBB033F00AB86E3 /* FABKitProtocol.h */; };
96E5170420005A6B00A02306 /* SMCalloutView.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848891CBB037E00AB86E3 /* SMCalloutView.h */; };
96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; };
+ AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; };
+ AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; };
+ AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; };
+ AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; };
+ CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; };
@@ -666,6 +678,8 @@
16376B3F1FFDB4B40000563E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
16376B401FFDB4B40000563E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewLayoutTests.m; sourceTree = ""; };
+ 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapColorTests.mm; path = ../../darwin/test/MGLHeatmapColorTests.mm; sourceTree = ""; };
+ 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapStyleLayerTests.mm; path = ../../darwin/test/MGLHeatmapStyleLayerTests.mm; sourceTree = ""; };
1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; };
1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; };
1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = ""; };
@@ -797,6 +811,8 @@
632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXEmbeddedMapViewController.h; sourceTree = ""; };
632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXEmbeddedMapViewController.m; sourceTree = ""; };
6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationExampleTests.swift; path = ../../darwin/test/MGLDocumentationExampleTests.swift; sourceTree = ""; };
+ 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = ""; };
+ 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = ""; };
920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = ""; };
927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXSnapshotsViewController.h; sourceTree = ""; };
927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXSnapshotsViewController.m; sourceTree = ""; };
@@ -831,6 +847,9 @@
96E0272D1E57C7E6004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; };
96E0272E1E57C7E7004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; };
96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = ""; };
+ AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = ""; };
+ AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = ""; };
+ CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; sourceTree = ""; };
DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = ""; };
DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = ""; };
DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = ""; };
@@ -1236,6 +1255,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 +1327,8 @@
3575798F1D513EF1000B822E /* Layers */ = {
isa = PBXGroup;
children = (
+ 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */,
+ 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */,
DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */,
DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */,
DA3C6FF21E2859E700F962BE /* test-Bridging-Header.h */,
@@ -1587,19 +1610,20 @@
DA8848331CBAFB2A00AB86E3 /* Kit */ = {
isa = PBXGroup;
children = (
- 355ADFF91E9281C300F3939D /* Views */,
- 35CE617F1D4165C2004F2359 /* Categories */,
DAD165841CF4D06B001FF4B9 /* Annotations */,
- DAD165851CF4D08B001FF4B9 /* Telemetry */,
- DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */,
- DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */,
- DA8848361CBAFB8500AB86E3 /* MGLMapView.h */,
- DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */,
- DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */,
- DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */,
- DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */,
+ 35CE617F1D4165C2004F2359 /* Categories */,
DA88487F1CBB033F00AB86E3 /* Fabric */,
DA8848881CBB036000AB86E3 /* SMCalloutView */,
+ DAD165851CF4D08B001FF4B9 /* Telemetry */,
+ 355ADFF91E9281C300F3939D /* Views */,
+ CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */,
+ DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */,
+ DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */,
+ DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */,
+ DA8848361CBAFB8500AB86E3 /* MGLMapView.h */,
+ DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */,
+ DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */,
+ DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */,
);
name = Kit;
path = src;
@@ -1858,6 +1882,8 @@
DA8848491CBAFB9800AB86E3 /* MGLMapboxEvents.m */,
9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */,
9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */,
+ AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */,
+ AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */,
);
name = Telemetry;
sourceTree = "";
@@ -1891,6 +1917,8 @@
350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */,
DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */,
404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */,
+ CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */,
+ 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */,
DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */,
35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */,
3510FFF01D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */,
@@ -1963,6 +1991,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 */,
@@ -1978,6 +2007,7 @@
DA8847F51CBAFA5100AB86E3 /* MGLOfflineRegion.h in Headers */,
DA737EE11D056A4E005BDA16 /* MGLMapViewDelegate.h in Headers */,
DA8848851CBB033F00AB86E3 /* FABKitProtocol.h in Headers */,
+ AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */,
DA88481B1CBAFA6200AB86E3 /* MGLGeometry_Private.h in Headers */,
3510FFF91D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */,
3557F7B01E1D27D300CCA5E6 /* MGLDistanceFormatter.h in Headers */,
@@ -2038,6 +2068,7 @@
96E516EF2000594F00A02306 /* NSArray+MGLAdditions.h in Headers */,
96E516F12000596800A02306 /* NSString+MGLAdditions.h in Headers */,
35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */,
+ CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */,
DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */,
DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */,
927FBD001F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */,
@@ -2068,6 +2099,7 @@
96E516FB20005A4000A02306 /* MGLUserLocationHeadingBeamLayer.h in Headers */,
96E516DC2000547000A02306 /* MGLPolyline_Private.h in Headers */,
353AFA151D65AB17005A69F4 /* NSDate+MGLAdditions.h in Headers */,
+ AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */,
3510FFFA1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */,
DA72620C1DEEE3480043BB89 /* MGLOpenGLStyleLayer.h in Headers */,
35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */,
@@ -2099,6 +2131,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 */,
@@ -2544,6 +2577,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 */,
@@ -2656,6 +2691,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 */,
@@ -2667,6 +2703,7 @@
DA72620D1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */,
DA88481A1CBAFA6200AB86E3 /* MGLAccountManager.m in Sources */,
3510FFFB1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */,
+ AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */,
DA8848271CBAFA6200AB86E3 /* MGLPolyline.mm in Sources */,
DA8848581CBAFB9800AB86E3 /* MGLMapboxEvents.m in Sources */,
35CE61841D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */,
@@ -2747,6 +2784,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 */,
@@ -2758,6 +2796,7 @@
DA72620E1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */,
DAA4E42F1CBB730400178DFB /* MGLCompactCalloutView.m in Sources */,
3510FFFC1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */,
+ AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */,
DAA4E4271CBB730400178DFB /* MGLTilePyramidOfflineRegion.mm in Sources */,
DAA4E41C1CBB730400178DFB /* MGLAccountManager.m in Sources */,
35CE61851D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */,
diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml
index b3662adc7..61e9ad39e 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/MGLCameraChangeReason.h b/platform/ios/src/MGLCameraChangeReason.h
new file mode 100644
index 000000000..6c6b3636b
--- /dev/null
+++ b/platform/ios/src/MGLCameraChangeReason.h
@@ -0,0 +1,61 @@
+#import "MGLFoundation.h"
+
+/**
+ :nodoc:
+ Bitmask values that describe why a camera move occurred.
+
+ Values of this type are passed to the `MGLMapView`'s delegate in the following methods:
+
+ - `-mapView:shouldChangeFromCamera:toCamera:reason:`
+ - `-mapView:regionWillChangeWithReason:animated:`
+ - `-mapView:regionIsChangingWithReason:`
+ - `-mapView:regionDidChangeWithReason:animated:`
+
+ It's important to note that it's almost impossible to perform a rotate without zooming (in or out),
+ so if you'll often find `MGLCameraChangeReasonGesturePinch` set alongside `MGLCameraChangeReasonGestureRotate`.
+
+ Since there are several reasons why a zoom or rotation has occurred, it is worth considering
+ creating a combined constant, for example:
+
+ ```
+ static const MGLCameraChangeReason anyZoom = MGLCameraChangeReasonGesturePinch |
+ MGLCameraChangeReasonGestureZoomIn |
+ MGLCameraChangeReasonGestureZoomOut |
+ MGLCameraChangeReasonGestureOneFingerZoom;
+
+ static const MGLCameraChangeReason anyRotation = MGLCameraChangeReasonResetNorth | MGLCameraChangeReasonGestureRotate;
+ ```
+ */
+typedef NS_OPTIONS(NSUInteger, MGLCameraChangeReason)
+{
+ /// :nodoc: The reason for the camera change has not be specified.
+ MGLCameraChangeReasonNone = 0,
+
+ /// :nodoc: Set when a public API that moves the camera is called. This may be set for some gestures,
+ /// for example MGLCameraChangeReasonResetNorth.
+ MGLCameraChangeReasonProgrammatic = 1 << 0,
+
+ /// :nodoc: The user tapped the compass to reset the map orientation so North is up.
+ MGLCameraChangeReasonResetNorth = 1 << 1,
+
+ /// :nodoc: The user panned the map.
+ MGLCameraChangeReasonGesturePan = 1 << 2,
+
+ /// :nodoc: The user pinched to zoom in/out.
+ MGLCameraChangeReasonGesturePinch = 1 << 3,
+
+ // :nodoc: The user rotated the map.
+ MGLCameraChangeReasonGestureRotate = 1 << 4,
+
+ /// :nodoc: The user zoomed the map in (one finger double tap).
+ MGLCameraChangeReasonGestureZoomIn = 1 << 5,
+
+ /// :nodoc: The user zoomed the map out (two finger single tap).
+ MGLCameraChangeReasonGestureZoomOut = 1 << 6,
+
+ /// :nodoc: The user long pressed on the map for a quick zoom (single tap, then long press and drag up/down).
+ MGLCameraChangeReasonGestureOneFingerZoom = 1 << 7,
+
+ // :nodoc: The user panned with two fingers to tilt the map (two finger drag).
+ MGLCameraChangeReasonGestureTilt = 1 << 8
+};
diff --git a/platform/ios/src/MGLLocationManager.m b/platform/ios/src/MGLLocationManager.m
index b0d2e17d5..85ef4ca48 100644
--- a/platform/ios/src/MGLLocationManager.m
+++ b/platform/ios/src/MGLLocationManager.m
@@ -1,9 +1,9 @@
#import "MGLLocationManager.h"
+#import "MGLTelemetryConfig.h"
#import
static const NSTimeInterval MGLLocationManagerHibernationTimeout = 300.0;
static const NSTimeInterval MGLLocationManagerHibernationPollInterval = 5.0;
-static const CLLocationDistance MGLLocationManagerHibernationRadius = 300.0;
static const CLLocationDistance MGLLocationManagerDistanceFilter = 5.0;
static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManagerRegionIdentifier.fence.center";
@@ -122,7 +122,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage
}
- (void)establishRegionMonitoringForLocation:(CLLocation *)location {
- CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier];
+ CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier];
region.notifyOnEntry = NO;
region.notifyOnExit = YES;
[self.standardLocationManager startMonitoringForRegion:region];
@@ -151,7 +151,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage
if (location.speed > 0.0) {
[self startBackgroundTimeoutTimer];
}
- if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLLocationManagerHibernationRadius) {
+ if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius) {
[self establishRegionMonitoringForLocation:location];
}
if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) {
diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm
index 1cbbc2287..79b02bef3 100644
--- a/platform/ios/src/MGLMapView.mm
+++ b/platform/ios/src/MGLMapView.mm
@@ -210,6 +210,8 @@ public:
@property (nonatomic) UILongPressGestureRecognizer *quickZoom;
@property (nonatomic) UIPanGestureRecognizer *twoFingerDrag;
+@property (nonatomic) MGLCameraChangeReason cameraChangeReasonBitmask;
+
/// Mapping from reusable identifiers to annotation images.
@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier;
@@ -506,10 +508,6 @@ public:
_doubleTap.numberOfTapsRequired = 2;
[self addGestureRecognizer:_doubleTap];
- _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)];
- [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap];
- _singleTapGestureRecognizer.delegate = self;
- [self addGestureRecognizer:_singleTapGestureRecognizer];
_twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)];
_twoFingerDrag.minimumNumberOfTouches = 2;
@@ -534,16 +532,25 @@ public:
[_quickZoom requireGestureRecognizerToFail:_doubleTap];
[self addGestureRecognizer:_quickZoom];
+ _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)];
+ [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap];
+ _singleTapGestureRecognizer.delegate = self;
+ [_singleTapGestureRecognizer requireGestureRecognizerToFail:_quickZoom];
+ [self addGestureRecognizer:_singleTapGestureRecognizer];
+
// observe app activity
//
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationWillEnterForegroundNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationDidBecomeActiveNotification object:nil];
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
+
// set initial position
//
mbgl::CameraOptions options;
@@ -551,6 +558,9 @@ public:
mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset);
options.padding = padding;
options.zoom = 0;
+
+ _cameraChangeReasonBitmask = MGLCameraChangeReasonNone;
+
_mbglMap->jumpTo(options);
_pendingLatitude = NAN;
_pendingLongitude = NAN;
@@ -706,7 +716,7 @@ public:
{
MGLAssertIsMainThread();
- _rendererFrontend->onLowMemory();
+ _rendererFrontend->reduceMemoryUse();
}
#pragma mark - Layout -
@@ -1148,6 +1158,13 @@ public:
- (void)sleepGL:(__unused NSNotification *)notification
{
MGLAssertIsMainThread();
+
+ // Ideally we would wait until we actually received a memory warning but the bulk of the memory
+ // we have to release is tied up in GL buffers that we can't touch once we're in the background.
+ // Compromise position: release everything but currently rendering tiles
+ // A possible improvement would be to store a copy of the GL buffers that we could use to rapidly
+ // restart, but that we could also discard in response to a memory warning.
+ _rendererFrontend->reduceMemoryUse();
if ( ! self.dormant)
{
@@ -1237,6 +1254,8 @@ public:
- (void)handleCompassTapGesture:(__unused id)sender
{
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonResetNorth;
+
[self resetNorthAnimated:YES];
if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading ||
@@ -1259,6 +1278,7 @@ public:
- (void)notifyGestureDidBegin {
BOOL animated = NO;
+
[self cameraWillChangeAnimated:animated];
_mbglMap->setGestureInProgress(true);
_changeDelimiterSuppressionDepth++;
@@ -1282,6 +1302,23 @@ public:
return _changeDelimiterSuppressionDepth > 0;
}
+- (BOOL)_shouldChangeFromCamera:(nonnull MGLMapCamera *)oldCamera toCamera:(nonnull MGLMapCamera *)newCamera
+{
+ // Check delegates first
+ if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:reason:)])
+ {
+ return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera reason:self.cameraChangeReasonBitmask];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)])
+ {
+ return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera];
+ }
+ else
+ {
+ return YES;
+ }
+}
+
- (void)handlePanGesture:(UIPanGestureRecognizer *)pan
{
if ( ! self.isScrollEnabled) return;
@@ -1289,7 +1326,9 @@ public:
_mbglMap->cancelTransitions();
MGLMapCamera *oldCamera = self.camera;
-
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePan;
+
if (pan.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan];
@@ -1303,9 +1342,8 @@ public:
CGPoint delta = [pan translationInView:pan.view];
MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->moveBy({ delta.x, delta.y });
[pan setTranslation:CGPointZero inView:pan.view];
@@ -1327,9 +1365,8 @@ public:
{
CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4);
MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->moveBy({ offset.x, offset.y }, MGLDurationFromTimeInterval(self.decelerationRate));
}
@@ -1360,6 +1397,8 @@ public:
CGPoint centerPoint = [self anchorPointForGesture:pinch];
MGLMapCamera *oldCamera = self.camera;
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePinch;
+
if (pinch.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGesturePinchStart forRecognizer:pinch];
@@ -1376,9 +1415,8 @@ public:
// Calculates the final camera zoom, has no effect within current map camera.
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
// The gesture recognizer only reports the gesture’s current center
@@ -1430,9 +1468,8 @@ public:
// Calculates the final camera zoom, this has no effect within current map camera.
double zoom = log2(newScale);
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint];
-
- if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]
- && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
drift = NO;
} else {
@@ -1458,7 +1495,9 @@ public:
CGPoint centerPoint = [self anchorPointForGesture:rotate];
MGLMapCamera *oldCamera = self.camera;
-
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureRotate;
+
if (rotate.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureRotateStart forRecognizer:rotate];
@@ -1485,9 +1524,8 @@ public:
}
MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
}
@@ -1505,9 +1543,8 @@ public:
CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1;
MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationFromTimeInterval(decelerationRate));
@@ -1552,6 +1589,7 @@ public:
}
[self deselectAnnotation:self.selectedAnnotation animated:YES];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement);
+
return;
}
@@ -1562,7 +1600,7 @@ public:
CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint];
[self selectAnnotation:annotation animated:YES calloutPositioningRect:positionRect];
}
- else
+ else if (self.selectedAnnotation)
{
[self deselectAnnotation:self.selectedAnnotation animated:YES];
}
@@ -1644,6 +1682,8 @@ public:
if (doubleTap.state == UIGestureRecognizerStateEnded)
{
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomIn;
+
MGLMapCamera *oldCamera = self.camera;
double newZoom = round(self.zoomLevel) + 1.0;
@@ -1651,9 +1691,8 @@ public:
CGPoint gesturePoint = [self anchorPointForGesture:doubleTap];
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
[self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap];
@@ -1680,9 +1719,13 @@ public:
_mbglMap->cancelTransitions();
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomOut;
+
if (twoFingerTap.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureTwoFingerSingleTap forRecognizer:twoFingerTap];
+
+ [self notifyGestureDidBegin];
}
else if (twoFingerTap.state == UIGestureRecognizerStateEnded)
{
@@ -1693,9 +1736,8 @@ public:
CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap];
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y);
_mbglMap->setZoom(newZoom, center, MGLDurationFromTimeInterval(MGLAnimationDuration));
@@ -1715,7 +1757,9 @@ public:
if ( ! self.isZoomEnabled) return;
_mbglMap->cancelTransitions();
-
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureOneFingerZoom;
+
if (quickZoom.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGestureQuickZoom forRecognizer:quickZoom];
@@ -1738,9 +1782,8 @@ public:
MGLMapCamera *oldCamera = self.camera;
MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint];
-
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
}
@@ -1760,6 +1803,8 @@ public:
_mbglMap->cancelTransitions();
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureTilt;
+
if (twoFingerDrag.state == UIGestureRecognizerStateBegan)
{
[self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag];
@@ -1779,8 +1824,7 @@ public:
MGLMapCamera *oldCamera = self.camera;
MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew];
- if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] ||
- [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera])
+ if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera])
{
_mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y });
}
@@ -2278,7 +2322,7 @@ public:
- (void)emptyMemoryCache
{
- _rendererFrontend->onLowMemory();
+ _rendererFrontend->reduceMemoryUse();
}
- (void)setZoomEnabled:(BOOL)zoomEnabled
@@ -2871,6 +2915,8 @@ public:
{
self.userTrackingMode = MGLUserTrackingModeNone;
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
[self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion];
}
@@ -2920,6 +2966,9 @@ public:
}
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
_mbglMap->easeTo(cameraOptions, animationOptions);
}
@@ -2943,6 +2992,8 @@ public:
if (zoomLevel == self.zoomLevel) return;
_mbglMap->cancelTransitions();
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
CGFloat duration = animated ? MGLAnimationDuration : 0;
_mbglMap->setZoom(zoomLevel,
@@ -3031,6 +3082,9 @@ public:
- (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion
{
self.userTrackingMode = MGLUserTrackingModeNone;
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
[self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion];
}
@@ -3080,6 +3134,9 @@ public:
[self willChangeValueForKey:@"visibleCoordinateBounds"];
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
_mbglMap->easeTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"visibleCoordinateBounds"];
}
@@ -3113,6 +3170,8 @@ public:
CGFloat duration = animated ? MGLAnimationDuration : 0;
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
if (self.userTrackingMode == MGLUserTrackingModeNone)
{
_mbglMap->setBearing(direction,
@@ -3197,6 +3256,9 @@ public:
[self willChangeValueForKey:@"camera"];
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding];
_mbglMap->easeTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -3253,6 +3315,9 @@ public:
[self willChangeValueForKey:@"camera"];
_mbglMap->cancelTransitions();
+
+ self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic;
+
mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets];
_mbglMap->flyTo(cameraOptions, animationOptions);
[self didChangeValueForKey:@"camera"];
@@ -3413,6 +3478,13 @@ public:
return [self metersPerPointAtLatitude:latitude];
}
+#pragma mark - Camera Change Reason -
+
+- (void)resetCameraChangeReason
+{
+ self.cameraChangeReasonBitmask = MGLCameraChangeReasonNone;
+}
+
#pragma mark - Styling -
- (NS_ARRAY_OF(NSURL *) *)bundledStyleURLs
@@ -5354,9 +5426,16 @@ public:
}
}
- if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)])
+ if ( ! [self isSuppressingChangeDelimiters] )
{
- [self.delegate mapView:self regionWillChangeAnimated:animated];
+ if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeWithReason:animated:)])
+ {
+ [self.delegate mapView:self regionWillChangeWithReason:self.cameraChangeReasonBitmask animated:animated];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)])
+ {
+ [self.delegate mapView:self regionWillChangeAnimated:animated];
+ }
}
}
@@ -5370,8 +5449,12 @@ public:
if (!self.scaleBar.hidden) {
[(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]];
}
-
- if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)])
+
+ if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)])
+ {
+ [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReasonBitmask];
+ }
+ else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)])
{
[self.delegate mapViewRegionIsChanging:self];
}
@@ -5384,9 +5467,13 @@ public:
[self updateCompass];
- if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)])
+ if ( ! [self isSuppressingChangeDelimiters])
{
- if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)
+ BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)];
+ BOOL respondsToSelectorWithReason = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeWithReason:animated:)];
+
+ if ((respondsToSelector || respondsToSelectorWithReason) &&
+ ([UIApplication sharedApplication].applicationState == UIApplicationStateActive))
{
_featureAccessibilityElements = nil;
_visiblePlaceFeatures = nil;
@@ -5398,7 +5485,17 @@ public:
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
}
}
- [self.delegate mapView:self regionDidChangeAnimated:animated];
+
+ if (respondsToSelectorWithReason)
+ {
+ [self.delegate mapView:self regionDidChangeWithReason:self.cameraChangeReasonBitmask animated:animated];
+ }
+ else if (respondsToSelector)
+ {
+ [self.delegate mapView:self regionDidChangeAnimated:animated];
+ }
+
+ [self resetCameraChangeReason];
}
}
diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h
index 096711fcb..0368d8413 100644
--- a/platform/ios/src/MGLMapViewDelegate.h
+++ b/platform/ios/src/MGLMapViewDelegate.h
@@ -1,6 +1,7 @@
#import
#import "MGLTypes.h"
+#import "MGLCameraChangeReason.h"
NS_ASSUME_NONNULL_BEGIN
@@ -21,17 +22,80 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Responding to Map Position Changes
+/**
+ Asks the delegate whether the map view should be allowed to change from the
+ existing camera to the new camera in response to a user gesture.
+
+ This method is called as soon as the user gesture is recognized. It is not
+ called in response to a programmatic camera change, such as by setting the
+ `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
+
+ This method is called many times during gesturing, so you should avoid performing
+ complex or performance-intensive tasks in your implementation.
+
+ @param mapView The map view that the user is manipulating.
+ @param oldCamera The camera representing the viewpoint at the moment the
+ gesture is recognized. If this method returns `NO`, the map view’s camera
+ continues to be this camera.
+ @param newCamera The expected camera after the gesture completes. If this
+ method returns `YES`, this camera becomes the map view’s camera.
+ @return A Boolean value indicating whether the map view should stay at
+ `oldCamera` or change to `newCamera`.
+ */
+- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera;
+
+/**
+ :nodoc:
+ Asks the delegate whether the map view should be allowed to change from the
+ existing camera to the new camera in response to a user gesture.
+
+ This method is called as soon as the user gesture is recognized. It is not
+ called in response to a programmatic camera change, such as by setting the
+ `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
+
+ This method is called many times during gesturing, so you should avoid performing
+ complex or performance-intensive tasks in your implementation.
+
+ @param mapView The map view that the user is manipulating.
+ @param oldCamera The camera representing the viewpoint at the moment the
+ gesture is recognized. If this method returns `NO`, the map view’s camera
+ continues to be this camera.
+ @param newCamera The expected camera after the gesture completes. If this
+ method returns `YES`, this camera becomes the map view’s camera.
+ @param reason The reason for the camera change.
+ @return A Boolean value indicating whether the map view should stay at
+ `oldCamera` or change to `newCamera`.
+
+ @note If this method is implemented `-mapView:shouldChangeFromCamera:toCamera:` will not be called.
+ */
+- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera reason:(MGLCameraChangeReason)reason;
+
/**
Tells the delegate that the viewpoint depicted by the map view is about to change.
This method is called whenever the currently displayed map camera will start
changing for any reason.
-
+
@param mapView The map view whose viewpoint will change.
@param animated Whether the change will cause an animated effect on the map.
*/
- (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated;
+/**
+ :nodoc:
+ Tells the delegate that the viewpoint depicted by the map view is about to change.
+
+ This method is called whenever the currently displayed map camera will start
+ changing for any reason.
+
+ @param mapView The map view whose viewpoint will change.
+ @param animated Whether the change will cause an animated effect on the map.
+ @param reason The reason for the camera change.
+
+ @note If this method is implemented `-mapView:regionWillChangeAnimated:` will not be called.
+ */
+- (void)mapView:(MGLMapView *)mapView regionWillChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated;
+
/**
Tells the delegate that the viewpoint depicted by the map view is changing.
@@ -48,6 +112,26 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)mapViewRegionIsChanging:(MGLMapView *)mapView;
+/**
+ :nodoc:
+ Tells the delegate that the viewpoint depicted by the map view is changing.
+
+ This method is called as the currently displayed map camera changes as part of
+ an animation, whether due to a user gesture or due to a call to a method such
+ as `-[MGLMapView setCamera:animated:]`. This method can be called before
+ `-mapViewDidFinishLoadingMap:` is called.
+
+ During the animation, this method may be called many times to report updates to
+ the viewpoint. Therefore, your implementation of this method should be as lightweight
+ as possible to avoid affecting performance.
+
+ @param mapView The map view whose viewpoint is changing.
+ @param reason The reason for the camera change.
+
+ @note If this method is implemented `-mapViewRegionIsChanging:` will not be called.
+ */
+- (void)mapView:(MGLMapView *)mapView regionIsChangingWithReason:(MGLCameraChangeReason)reason;
+
/**
Tells the delegate that the viewpoint depicted by the map view has finished
changing.
@@ -62,26 +146,21 @@ NS_ASSUME_NONNULL_BEGIN
- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated;
/**
- Asks the delegate whether the map view should be allowed to change from the
- existing camera to the new camera in response to a user gesture.
-
- This method is called as soon as the user gesture is recognized. It is not
- called in response to a programmatic camera change, such as by setting the
- `centerCoordinate` property or calling `-flyToCamera:completionHandler:`.
-
- This method is called many times during gesturing, so you should avoid performing
- complex or performance-intensive tasks in your implementation.
-
- @param mapView The map view that the user is manipulating.
- @param oldCamera The camera representing the viewpoint at the moment the
- gesture is recognized. If this method returns `NO`, the map view’s camera
- continues to be this camera.
- @param newCamera The expected camera after the gesture completes. If this
- method returns `YES`, this camera becomes the map view’s camera.
- @return A Boolean value indicating whether the map view should stay at
- `oldCamera` or change to `newCamera`.
+ :nodoc:
+ Tells the delegate that the viewpoint depicted by the map view has finished
+ changing.
+
+ This method is called whenever the currently displayed map camera has finished
+ changing, after any calls to `-mapViewRegionIsChanging:` due to animation. Therefore,
+ this method can be called before `-mapViewDidFinishLoadingMap:` is called.
+
+ @param mapView The map view whose viewpoint has changed.
+ @param animated Whether the change caused an animated effect on the map.
+ @param reason The reason for the camera change.
+
+ @note If this method is implemented `-mapView:regionDidChangeAnimated:` will not be called.
*/
-- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera;
+- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated;
#pragma mark Loading the Map
diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m
index d59972f5b..273af5b3b 100644
--- a/platform/ios/src/MGLMapboxEvents.m
+++ b/platform/ios/src/MGLMapboxEvents.m
@@ -6,6 +6,7 @@
#import "NSException+MGLAdditions.h"
#import "MGLAPIClient.h"
#import "MGLLocationManager.h"
+#import "MGLTelemetryConfig.h"
#include
#include
@@ -172,6 +173,8 @@ const NSTimeInterval MGLFlushInterval = 180;
- (instancetype) init {
self = [super init];
if (self) {
+ [MGLTelemetryConfig.sharedConfig configurationFromKey:[[NSUserDefaults standardUserDefaults] objectForKey:MGLMapboxMetricsProfile]];
+
_currentAccountTypeValue = @0;
_currentMetricsEnabledValue = YES;
diff --git a/platform/ios/src/MGLTelemetryConfig.h b/platform/ios/src/MGLTelemetryConfig.h
new file mode 100644
index 000000000..527d34429
--- /dev/null
+++ b/platform/ios/src/MGLTelemetryConfig.h
@@ -0,0 +1,18 @@
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MGLTelemetryConfig : NSObject
+
+@property (nonatomic) CLLocationDistance MGLLocationManagerHibernationRadius;
+
+extern NSString *const MGLMapboxMetricsProfile;
+
++ (nullable instancetype)sharedConfig;
+
+- (void)configurationFromKey:(NSString *)key;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/ios/src/MGLTelemetryConfig.m b/platform/ios/src/MGLTelemetryConfig.m
new file mode 100644
index 000000000..828bafb14
--- /dev/null
+++ b/platform/ios/src/MGLTelemetryConfig.m
@@ -0,0 +1,35 @@
+#import "MGLTelemetryConfig.h"
+
+static const CLLocationDistance MGLConfigHibernationRadiusDefault = 300.0;
+static const CLLocationDistance MGLConfigHibernationRadiusWide = 600.0;
+
+NSString *const MGLMapboxMetricsProfile = @"MGLMapboxMetricsProfile";
+
+static NSString *const MGLConfigHibernationRadiusWideKey = @"WideGeoFence";
+
+@implementation MGLTelemetryConfig
+
+- (instancetype) init {
+ self = [super init];
+ if (self) {
+ _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusDefault;
+ }
+ return self;
+}
+
++ (nullable instancetype)sharedConfig {
+ static dispatch_once_t onceToken;
+ static MGLTelemetryConfig *_sharedConfig;
+ dispatch_once(&onceToken, ^{
+ _sharedConfig = [[self alloc] init];
+ });
+ return _sharedConfig;
+}
+
+- (void)configurationFromKey:(NSString *)key {
+ if ([key isEqualToString:MGLConfigHibernationRadiusWideKey]) {
+ _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusWide;
+ }
+}
+
+@end
diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h
index d105189bb..11720ac68 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/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift
index 50f101e86..4d11b000b 100644
--- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift
+++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift
@@ -13,6 +13,10 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate {
func mapViewRegionIsChanging(_ mapView: MGLMapView) {}
+ func mapViewRegionIsChanging(_ mapView: MGLMapView, reason: MGLCameraChangeReason) {}
+
+ func mapView(_ mapView: MGLMapView, regionIsChangingWith reason: MGLCameraChangeReason) {}
+
func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) {}
func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {}
@@ -33,10 +37,16 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate {
func mapView(_ mapView: MGLMapView, didDeselect annotation: MGLAnnotation) {}
+ func mapView(_ mapView: MGLMapView, didSingleTapAt coordinate: CLLocationCoordinate2D) {}
+
func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {}
+ func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) {}
+
func mapView(_ mapView: MGLMapView, regionWillChangeAnimated animated: Bool) {}
+ func mapView(_ mapView: MGLMapView, regionWillChangeWith reason: MGLCameraChangeReason, animated: Bool) {}
+
func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {}
func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {}
@@ -79,4 +89,5 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate {
func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool { return false }
+ func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera, reason: MGLCameraChangeReason) -> Bool { return false }
}
diff --git a/platform/ios/uitest/MapViewTests.m b/platform/ios/uitest/MapViewTests.m
index 4ed3d8939..ba15af918 100644
--- a/platform/ios/uitest/MapViewTests.m
+++ b/platform/ios/uitest/MapViewTests.m
@@ -538,10 +538,13 @@
userInfo:@{ @"animated" : @(animated) }];
}
-- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
+- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated {
+
[[NSNotificationCenter defaultCenter] postNotificationName:@"regionDidChangeAnimated"
object:mapView
- userInfo:@{ @"animated" : @(animated) }];
+ userInfo:@{ @"animated" : @(animated),
+ @"reason" : @(reason)
+ }];
}
- (void)testDelegatesStartStopLocatingUser {
diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md
index d35f396a2..0df8e9a80 100644
--- a/platform/macos/CHANGELOG.md
+++ b/platform/macos/CHANGELOG.md
@@ -4,20 +4,24 @@
### 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))
* The `MGLSymbolStyleLayer.textFontNames` property can now depend on a feature’s attributes. ([#10850](https://github.com/mapbox/mapbox-gl-native/pull/10850))
+* Properties such as `MGLSymbolStyleLayer.iconAllowsOverlap` and `MGLSymbolStyleLayer.iconIgnoresPlacement` now account for symbols in other sources. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
### Map rendering
* Improved the reliability of collision detection between symbols near the edges of tiles, as well as between symbols when the map is tilted. It is no longer necessary to enable `MGLSymbolStyleLayer.symbolAvoidsEdges` to prevent symbols in adjacent tiles from overlapping with each other. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
* Symbols can fade in and out as the map pans, rotates, or tilts. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
* Properties such as `MGLSymbolStyleLayer.iconAllowsOverlap` and `MGLSymbolStyleLayer.iconIgnoresPlacement` now account for symbols in other sources. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436))
+* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141))
* Fixed an issue preventing a dynamically-added `MGLRasterStyleLayer` from drawing until the map pans. ([#10270](https://github.com/mapbox/mapbox-gl-native/pull/10270))
* Fixed an issue preventing `MGLImageSource`s from drawing on the map when the map is zoomed in and tilted. ([#10677](https://github.com/mapbox/mapbox-gl-native/pull/10677))
* Improved the sharpness of raster tiles on Retina displays. ([#10984](https://github.com/mapbox/mapbox-gl-native/pull/10984))
* Fixed a crash parsing a malformed style. ([#11001](https://github.com/mapbox/mapbox-gl-native/pull/11001))
+* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141))
### Map snapshots
@@ -28,6 +32,7 @@
* Added Danish and Hebrew localizations. ([#10967](https://github.com/mapbox/mapbox-gl-native/pull/10967), [#11136](https://github.com/mapbox/mapbox-gl-native/pull/11134))
* Feature querying results now account for the `MGLSymbolStyleLayer.circleStrokeWidth` property. ([#10897](https://github.com/mapbox/mapbox-gl-native/pull/10897))
+* Added a Hebrew localization. ([#10967](https://github.com/mapbox/mapbox-gl-native/pull/10967))
## v0.6.1 - January 16, 2018
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 000000000..10226ca23
--- /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 000000000..88195c373
Binary files /dev/null and b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf differ
diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m
index 602ed1aa4..7d39f9334 100644
--- a/platform/macos/app/MapDocument.m
+++ b/platform/macos/app/MapDocument.m
@@ -726,8 +726,14 @@ NS_ARRAY_OF(id ) *MBXFlattenedShapes(NS_ARRAY_OF(id
+
+
+
+
+
+
+
+