diff options
author | Jordan Kiley <jmkiley@users.noreply.github.com> | 2018-02-16 12:40:06 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-02-16 12:40:06 -0800 |
commit | d583cabfae9c5fb4b0e3d0298c757c847a789b93 (patch) | |
tree | f8d47dc6eb1b1fcce773ee7f0e3fdfb59ab8b31d | |
parent | ce523c074fa03d47f6e8e9c96fa4afcf384deeb5 (diff) | |
parent | 12103c95c03c8111995dd2ef4b9c7fb8ab671252 (diff) | |
download | qtlocation-mapboxgl-d583cabfae9c5fb4b0e3d0298c757c847a789b93.tar.gz |
Merge branch 'release-boba' into jmkiley-mark-unavailable
189 files changed, 6324 insertions, 776 deletions
diff --git a/INSTALL.md b/INSTALL.md index fb619770a1..a28348d15c 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 - - - 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`) +## 2: Getting the source -### Optional dependencies + Clone the git repository: -- [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 24d5262799..f24482e301 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -146,6 +146,10 @@ set(MBGL_CORE_FILES src/mbgl/programs/fill_extrusion_program.hpp src/mbgl/programs/fill_program.cpp src/mbgl/programs/fill_program.hpp + src/mbgl/programs/heatmap_program.cpp + src/mbgl/programs/heatmap_program.hpp + src/mbgl/programs/heatmap_texture_program.cpp + src/mbgl/programs/heatmap_texture_program.hpp src/mbgl/programs/hillshade_prepare_program.cpp src/mbgl/programs/hillshade_prepare_program.hpp src/mbgl/programs/hillshade_program.cpp @@ -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 db28ee92fc..798928087a 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 index 551b5c803e..551b5c803e 100644 --- a/src/mbgl/renderer/renderer_observer.hpp +++ b/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 73b141e799..dedecd1aa4 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<CustomGeometrySource::Options> { } } + const auto wrapValue = objectMember(value, "wrap"); + if (wrapValue) { + if (toBool(*wrapValue)) { + options.tileOptions.wrap = static_cast<bool>(*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<double>(*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 0000000000..e3689c524c --- /dev/null +++ b/include/mbgl/style/conversion/heatmap_color_property_value.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include <mbgl/style/heatmap_color_property_value.hpp> +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/constant.hpp> +#include <mbgl/style/conversion/function.hpp> +#include <mbgl/style/conversion/expression.hpp> +#include <mbgl/style/expression/value.hpp> +#include <mbgl/style/expression/is_constant.hpp> +#include <mbgl/style/expression/is_expression.hpp> +#include <mbgl/style/expression/find_zoom_curve.hpp> + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter<HeatmapColorPropertyValue> { + optional<HeatmapColorPropertyValue> operator()(const Convertible& value, Error& error) const { + if (isUndefined(value)) { + return HeatmapColorPropertyValue(); + } else if (isExpression(value)) { + optional<std::unique_ptr<Expression>> expression = convert<std::unique_ptr<Expression>>(value, error, expression::type::Color); + if (!expression) { + return {}; + } + assert(*expression); + if (!isFeatureConstant(**expression)) { + error = { "property expressions not supported" }; + return {}; + } + if (!isZoomConstant(**expression)) { + error = { "zoom expressions not supported" }; + return {}; + } + return {HeatmapColorPropertyValue(std::move(*expression))}; + } else { + error = { "heatmap-color must be an expression" }; + return {}; + } + } +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/heatmap_color_property_value.hpp b/include/mbgl/style/heatmap_color_property_value.hpp new file mode 100644 index 0000000000..130639c6e2 --- /dev/null +++ b/include/mbgl/style/heatmap_color_property_value.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include <mbgl/util/variant.hpp> +#include <mbgl/style/undefined.hpp> +#include <mbgl/style/function/camera_function.hpp> + +namespace mbgl { +namespace style { + +/* + * Special-case implementation of (a subset of) the PropertyValue<T> interface + * used for building the HeatmapColor paint property traits class. + */ +class HeatmapColorPropertyValue { +private: + std::shared_ptr<expression::Expression> value; + + friend bool operator==(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return (lhs.isUndefined() && rhs.isUndefined()) || (lhs.value && rhs.value && *(lhs.value) == *(rhs.value)); + } + + friend bool operator!=(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return !(lhs == rhs); + } + +public: + HeatmapColorPropertyValue() : value(nullptr) {} + HeatmapColorPropertyValue(std::shared_ptr<expression::Expression> value_) : value(std::move(value_)) {} + + bool isUndefined() const { return value.get() == nullptr; } + + // noop, needed for batch evaluation of paint property values to compile + template <typename Evaluator> + Color evaluate(const Evaluator&, TimePoint = {}) const { return {}; } + + Color evaluate(double heatmapDensity) const { + const auto result = value->evaluate(expression::EvaluationContext({}, nullptr, {heatmapDensity})); + return *expression::fromExpressionValue<Color>(*result); + } + + bool isDataDriven() const { return false; } + bool hasDataDrivenPropertyDifference(const HeatmapColorPropertyValue&) const { return false; } + + const expression::Expression& getExpression() const { return *value; } +}; + + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp index 8a5a4236b3..12494f5387 100644 --- a/include/mbgl/style/layer.hpp +++ b/include/mbgl/style/layer.hpp @@ -23,6 +23,7 @@ class HillshadeLayer; class BackgroundLayer; class CustomLayer; class FillExtrusionLayer; +class HeatmapLayer; class LayerObserver; /** @@ -93,6 +94,8 @@ public: return std::forward<V>(visitor)(*as<CustomLayer>()); case LayerType::FillExtrusion: return std::forward<V>(visitor)(*as<FillExtrusionLayer>()); + case LayerType::Heatmap: + return std::forward<V>(visitor)(*as<HeatmapLayer>()); } diff --git a/include/mbgl/style/layer_type.hpp b/include/mbgl/style/layer_type.hpp index 951757134b..0987ea4d0a 100644 --- a/include/mbgl/style/layer_type.hpp +++ b/include/mbgl/style/layer_type.hpp @@ -13,6 +13,7 @@ enum class LayerType { Background, Custom, FillExtrusion, + Heatmap, }; } // namespace style diff --git a/include/mbgl/style/layers/heatmap_layer.hpp b/include/mbgl/style/layers/heatmap_layer.hpp new file mode 100644 index 0000000000..33d927ad38 --- /dev/null +++ b/include/mbgl/style/layers/heatmap_layer.hpp @@ -0,0 +1,86 @@ +// This file is generated. Do not edit. + +#pragma once + +#include <mbgl/style/layer.hpp> +#include <mbgl/style/filter.hpp> +#include <mbgl/style/property_value.hpp> +#include <mbgl/style/data_driven_property_value.hpp> +#include <mbgl/style/heatmap_color_property_value.hpp> + +#include <mbgl/util/color.hpp> + +namespace mbgl { +namespace style { + +class TransitionOptions; + +class HeatmapLayer : public Layer { +public: + HeatmapLayer(const std::string& layerID, const std::string& sourceID); + ~HeatmapLayer() final; + + // Source + const std::string& getSourceID() const; + const std::string& getSourceLayer() const; + void setSourceLayer(const std::string& sourceLayer); + + void setFilter(const Filter&); + const Filter& getFilter() const; + + // Visibility + void setVisibility(VisibilityType) final; + + // Zoom range + void setMinZoom(float) final; + void setMaxZoom(float) final; + + // Paint properties + + static DataDrivenPropertyValue<float> getDefaultHeatmapRadius(); + DataDrivenPropertyValue<float> getHeatmapRadius() const; + void setHeatmapRadius(DataDrivenPropertyValue<float>); + void setHeatmapRadiusTransition(const TransitionOptions&); + TransitionOptions getHeatmapRadiusTransition() const; + + static DataDrivenPropertyValue<float> getDefaultHeatmapWeight(); + DataDrivenPropertyValue<float> getHeatmapWeight() const; + void setHeatmapWeight(DataDrivenPropertyValue<float>); + void setHeatmapWeightTransition(const TransitionOptions&); + TransitionOptions getHeatmapWeightTransition() const; + + static PropertyValue<float> getDefaultHeatmapIntensity(); + PropertyValue<float> getHeatmapIntensity() const; + void setHeatmapIntensity(PropertyValue<float>); + void setHeatmapIntensityTransition(const TransitionOptions&); + TransitionOptions getHeatmapIntensityTransition() const; + + static HeatmapColorPropertyValue getDefaultHeatmapColor(); + HeatmapColorPropertyValue getHeatmapColor() const; + void setHeatmapColor(HeatmapColorPropertyValue); + void setHeatmapColorTransition(const TransitionOptions&); + TransitionOptions getHeatmapColorTransition() const; + + static PropertyValue<float> getDefaultHeatmapOpacity(); + PropertyValue<float> getHeatmapOpacity() const; + void setHeatmapOpacity(PropertyValue<float>); + void setHeatmapOpacityTransition(const TransitionOptions&); + TransitionOptions getHeatmapOpacityTransition() const; + + // Private implementation + + class Impl; + const Impl& impl() const; + + Mutable<Impl> mutableImpl() const; + HeatmapLayer(Immutable<Impl>); + std::unique_ptr<Layer> cloneRef(const std::string& id) const final; +}; + +template <> +inline bool Layer::is<HeatmapLayer>() const { + return getType() == LayerType::Heatmap; +} + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs index 265dd57e1f..6d40405ccd 100644 --- a/include/mbgl/style/layers/layer.hpp.ejs +++ b/include/mbgl/style/layers/layer.hpp.ejs @@ -11,6 +11,9 @@ #include <mbgl/style/filter.hpp> #include <mbgl/style/property_value.hpp> #include <mbgl/style/data_driven_property_value.hpp> +<% if (type === 'heatmap') { -%> +#include <mbgl/style/heatmap_color_property_value.hpp> +<% } -%> #include <mbgl/util/color.hpp> diff --git a/include/mbgl/style/sources/custom_geometry_source.hpp b/include/mbgl/style/sources/custom_geometry_source.hpp index a0b990b44b..9daeeb3819 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 1b7b8f0f75..ed2b907647 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<std::string> tiles; Range<uint8_t> zoomRange; std::string attribution; Scheme scheme; + // DEMEncoding is not supported by the TileJSON spec + DEMEncoding encoding; optional<LatLngBounds> bounds; + Tileset(std::vector<std::string> tiles_ = std::vector<std::string>(), Range<uint8_t> 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 -Subproject de365184e13c08fb42bbd93a08abfc859829499 +Subproject d61850a6bd84ce6d697be392c7ef6d4b5555c20 diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 7b77582dd0..fa9b1d29cc 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,6 +2,10 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. +## master + + - HeatmapLayer [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046) + ## 6.0.0-beta.2 - February 13, 2018 - Deprecate LocationEngine [#11185](https://github.com/mapbox/mapbox-gl-native/pull/11185) - Remove LOST from SDK [11186](https://github.com/mapbox/mapbox-gl-native/pull/11186) @@ -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 648f26ce25..1c61748322 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/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro index b5a1d82c81..3b8adac5a8 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 a921d14038..cf70300c6e 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/style/layers/HeatmapLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java new file mode 100644 index 0000000000..6b8fd65def --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java @@ -0,0 +1,221 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +package com.mapbox.mapboxsdk.style.layers; + +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; + +import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; + +import com.mapbox.mapboxsdk.style.layers.TransitionOptions; + +/** + * A heatmap. + * + * @see <a href="https://www.mapbox.com/mapbox-gl-style-spec/#layers-heatmap">The online documentation</a> + */ +@UiThread +public class HeatmapLayer extends Layer { + + /** + * Creates a HeatmapLayer. + * + * @param nativePtr pointer used by core + */ + public HeatmapLayer(long nativePtr) { + super(nativePtr); + } + + /** + * Creates a HeatmapLayer. + * + * @param layerId the id of the layer + * @param sourceId the id of the source + */ + public HeatmapLayer(String layerId, String sourceId) { + initialize(layerId, sourceId); + } + + protected native void initialize(String layerId, String sourceId); + + /** + * Set the source layer. + * + * @param sourceLayer the source layer to set + */ + public void setSourceLayer(String sourceLayer) { + nativeSetSourceLayer(sourceLayer); + } + + /** + * Set the source Layer. + * + * @param sourceLayer the source layer to set + * @return This + */ + public HeatmapLayer withSourceLayer(String sourceLayer) { + setSourceLayer(sourceLayer); + return this; + } + + /** + * Get the source layer. + * + * @return sourceLayer the source layer to get + */ + public String getSourceLayer() { + return nativeGetSourceLayer(); + } + + /** + * Set a single filter. + * + * @param filter the filter to set + */ + public void setFilter(Filter.Statement filter) { + nativeSetFilter(filter.toArray()); + } + + /** + * Set a single filter. + * + * @param filter the filter to set + * @return This + */ + public HeatmapLayer withFilter(Filter.Statement filter) { + setFilter(filter); + return this; + } + + /** + * Set a property or properties. + * + * @param properties the var-args properties + * @return This + */ + public HeatmapLayer withProperties(@NonNull PropertyValue<?>... properties) { + setProperties(properties); + return this; + } + + // Property getters + + /** + * Get the HeatmapRadius property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue<Float> getHeatmapRadius() { + return (PropertyValue<Float>) new PropertyValue("heatmap-radius", nativeGetHeatmapRadius()); + } + + /** + * Get the HeatmapRadius property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapRadiusTransition() { + return nativeGetHeatmapRadiusTransition(); + } + + /** + * Set the HeatmapRadius property transition options + * + * @param options transition options for Float + */ + public void setHeatmapRadiusTransition(TransitionOptions options) { + nativeSetHeatmapRadiusTransition(options.getDuration(), options.getDelay()); + } + + /** + * Get the HeatmapWeight property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue<Float> getHeatmapWeight() { + return (PropertyValue<Float>) new PropertyValue("heatmap-weight", nativeGetHeatmapWeight()); + } + + /** + * Get the HeatmapIntensity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue<Float> getHeatmapIntensity() { + return (PropertyValue<Float>) new PropertyValue("heatmap-intensity", nativeGetHeatmapIntensity()); + } + + /** + * Get the HeatmapIntensity property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapIntensityTransition() { + return nativeGetHeatmapIntensityTransition(); + } + + /** + * Set the HeatmapIntensity property transition options + * + * @param options transition options for Float + */ + public void setHeatmapIntensityTransition(TransitionOptions options) { + nativeSetHeatmapIntensityTransition(options.getDuration(), options.getDelay()); + } + + /** + * Get the HeatmapOpacity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue<Float> getHeatmapOpacity() { + return (PropertyValue<Float>) new PropertyValue("heatmap-opacity", nativeGetHeatmapOpacity()); + } + + /** + * Get the HeatmapOpacity property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapOpacityTransition() { + return nativeGetHeatmapOpacityTransition(); + } + + /** + * Set the HeatmapOpacity property transition options + * + * @param options transition options for Float + */ + public void setHeatmapOpacityTransition(TransitionOptions options) { + nativeSetHeatmapOpacityTransition(options.getDuration(), options.getDelay()); + } + + private native Object nativeGetHeatmapRadius(); + + private native TransitionOptions nativeGetHeatmapRadiusTransition(); + + private native void nativeSetHeatmapRadiusTransition(long duration, long delay); + + private native Object nativeGetHeatmapWeight(); + + private native Object nativeGetHeatmapIntensity(); + + private native TransitionOptions nativeGetHeatmapIntensityTransition(); + + private native void nativeSetHeatmapIntensityTransition(long duration, long delay); + + private native Object nativeGetHeatmapOpacity(); + + private native TransitionOptions nativeGetHeatmapOpacityTransition(); + + private native void nativeSetHeatmapOpacityTransition(long duration, long delay); + + @Override + protected native void finalize() throws Throwable; + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java index dc65cb5081..7e3d3a7779 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 <a href="https://www.mapbox.com/mapbox-gl-style-spec/#layers-hillshade">The online documentation</a> */ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java index 6e644c5591..18ee05e63b 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java @@ -1516,6 +1516,138 @@ public class PropertyFactory { } /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue<Float> heatmapRadius(Float value) { + return new PaintPropertyValue<>("heatmap-radius", value); + } + + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue<Expression> heatmapRadius(Expression expression) { + return new PaintPropertyValue<>("heatmap-radius", expression); + } + + + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param <T> the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <T> PropertyValue<Function<T, Float>> heatmapRadius(Function<T, Float> function) { + return new PaintPropertyValue<>("heatmap-radius", function); + } + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue<Float> heatmapWeight(Float value) { + return new PaintPropertyValue<>("heatmap-weight", value); + } + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue<Expression> heatmapWeight(Expression expression) { + return new PaintPropertyValue<>("heatmap-weight", expression); + } + + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param <T> the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <T> PropertyValue<Function<T, Float>> heatmapWeight(Function<T, Float> function) { + return new PaintPropertyValue<>("heatmap-weight", function); + } + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue<Float> heatmapIntensity(Float value) { + return new PaintPropertyValue<>("heatmap-intensity", value); + } + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue<Expression> heatmapIntensity(Expression expression) { + return new PaintPropertyValue<>("heatmap-intensity", expression); + } + + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param <Z> the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> heatmapIntensity(CameraFunction<Z, Float> function) { + return new PaintPropertyValue<>("heatmap-intensity", function); + } + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue<Float> heatmapOpacity(Float value) { + return new PaintPropertyValue<>("heatmap-opacity", value); + } + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue<Expression> heatmapOpacity(Expression expression) { + return new PaintPropertyValue<>("heatmap-opacity", expression); + } + + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param <Z> the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> heatmapOpacity(CameraFunction<Z, Float> function) { + return new PaintPropertyValue<>("heatmap-opacity", function); + } + + /** * The opacity of the entire fill extrusion layer. This is rendered on a per-layer, not per-feature, basis, and data-driven styling is not available. * * @param value a Float value diff --git a/platform/android/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 3467ed7a7a..1b0999ae2e 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. * <p>Supported options are minZoom, maxZoom, buffer, and tolerance.</p> * * @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 0000000000..4ada38c238 --- /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 0000000000..364c8d2679 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java @@ -0,0 +1,626 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +package com.mapbox.mapboxsdk.testapp.style; + +import android.graphics.Color; +import android.support.test.espresso.UiController; +import android.support.test.runner.AndroidJUnit4; + +import timber.log.Timber; + +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.style.functions.CompositeFunction; +import com.mapbox.mapboxsdk.style.functions.CameraFunction; +import com.mapbox.mapboxsdk.style.functions.SourceFunction; +import com.mapbox.mapboxsdk.style.functions.stops.CategoricalStops; +import com.mapbox.mapboxsdk.style.functions.stops.ExponentialStops; +import com.mapbox.mapboxsdk.style.functions.stops.IdentityStops; +import com.mapbox.mapboxsdk.style.functions.stops.IntervalStops; +import com.mapbox.mapboxsdk.style.functions.stops.Stop; +import com.mapbox.mapboxsdk.style.functions.stops.Stops; +import com.mapbox.mapboxsdk.style.layers.HeatmapLayer; +import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction; +import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.mapbox.mapboxsdk.style.functions.Function.*; +import static com.mapbox.mapboxsdk.style.functions.stops.Stop.stop; +import static com.mapbox.mapboxsdk.style.functions.stops.Stops.*; +import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke; +import static org.junit.Assert.*; +import static com.mapbox.mapboxsdk.style.layers.Property.*; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.*; + +import com.mapbox.mapboxsdk.style.layers.TransitionOptions; +import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity; + +/** + * Basic smoke tests for HeatmapLayer + */ +@RunWith(AndroidJUnit4.class) +public class HeatmapLayerTest extends BaseActivityTest { + + private HeatmapLayer layer; + + @Override + protected Class getActivityClass() { + return EspressoTestActivity.class; + } + + private void setupLayer() { + Timber.i("Retrieving layer"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + if ((layer = mapboxMap.getLayerAs("my-layer")) == null) { + Timber.i("Adding layer"); + layer = new HeatmapLayer("my-layer", "composite"); + layer.setSourceLayer("composite"); + mapboxMap.addLayer(layer); + // Layer reference is now stale, get new reference + layer = mapboxMap.getLayerAs("my-layer"); + } + } + }); + } + + @Test + public void testSetVisibility() { + validateTestSetup(); + setupLayer(); + Timber.i("Visibility"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getVisibility().getValue(), VISIBLE); + + // Set + layer.setProperties(visibility(NONE)); + assertEquals(layer.getVisibility().getValue(), NONE); + } + }); + } + + @Test + public void testSourceLayer() { + validateTestSetup(); + setupLayer(); + Timber.i("SourceLayer"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getSourceLayer(), "composite"); + + // Set + final String sourceLayer = "test"; + layer.setSourceLayer(sourceLayer); + assertEquals(layer.getSourceLayer(), sourceLayer); + } + }); + } + + @Test + public void testHeatmapRadiusTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radiusTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapRadiusTransition(options); + assertEquals(layer.getHeatmapRadiusTransition(), options); + } + }); + } + + @Test + public void testHeatmapRadiusAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapRadius(0.3f)); + assertEquals((Float) layer.getHeatmapRadius().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapRadiusAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + zoom( + exponential( + stop(2, heatmapRadius(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapRadiusAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius(property("FeaturePropertyA", Stops.<Float>identity())) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapRadiusAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, heatmapRadius(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapRadiusAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, heatmapRadius(0.3f)) + ) + ).withDefaultValue(heatmapRadius(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testHeatmapRadiusAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, heatmapRadius(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(heatmapRadius(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(CompositeFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size()); + + ExponentialStops<Stop.CompositeValue<Float, Float>, Float> stops = + (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getHeatmapRadius().getFunction().getStops(); + Stop<Stop.CompositeValue<Float, Float>, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test + public void testHeatmapWeightAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapWeight(0.3f)); + assertEquals((Float) layer.getHeatmapWeight().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapWeightAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + zoom( + exponential( + stop(2, heatmapWeight(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapWeightAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight(property("FeaturePropertyA", Stops.<Float>identity())) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapWeightAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, heatmapWeight(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapWeightAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, heatmapWeight(0.3f)) + ) + ).withDefaultValue(heatmapWeight(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testHeatmapWeightAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, heatmapWeight(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(heatmapWeight(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(CompositeFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size()); + + ExponentialStops<Stop.CompositeValue<Float, Float>, Float> stops = + (ExponentialStops<Stop.CompositeValue<Float, Float>, Float>) layer.getHeatmapWeight().getFunction().getStops(); + Stop<Stop.CompositeValue<Float, Float>, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test + public void testHeatmapIntensityTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensityTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapIntensityTransition(options); + assertEquals(layer.getHeatmapIntensityTransition(), options); + } + }); + } + + @Test + public void testHeatmapIntensityAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapIntensity(0.3f)); + assertEquals((Float) layer.getHeatmapIntensity().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapIntensityAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapIntensity( + zoom( + exponential( + stop(2, heatmapIntensity(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapIntensity()); + assertNotNull(layer.getHeatmapIntensity().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapIntensity().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapIntensity().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapOpacityTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacityTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapOpacityTransition(options); + assertEquals(layer.getHeatmapOpacityTransition(), options); + } + }); + } + + @Test + public void testHeatmapOpacityAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapOpacity(0.3f)); + assertEquals((Float) layer.getHeatmapOpacity().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapOpacityAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapOpacity( + zoom( + exponential( + stop(2, heatmapOpacity(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapOpacity()); + assertNotNull(layer.getHeatmapOpacity().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapOpacity().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapOpacity().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).size()); + } + }); + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 9e64e03e11..0b6d2a155d 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"/> </activity> + <activity + android:name=".activity.style.HeatmapLayerActivity" + android:description="@string/description_heatmaplayer" + android:label="@string/activity_heatmaplayer"> + <meta-data + android:name="@string/category" + android:value="@string/category_style"/> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".activity.FeatureOverviewActivity"/> + </activity> <!-- For Instrumentation tests --> <activity diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java new file mode 100644 index 0000000000..b42734ea67 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/style/HeatmapLayerActivity.java @@ -0,0 +1,216 @@ +package com.mapbox.mapboxsdk.testapp.activity.style; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.style.layers.CircleLayer; +import com.mapbox.mapboxsdk.style.layers.HeatmapLayer; +import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; +import com.mapbox.mapboxsdk.testapp.R; + +import java.net.MalformedURLException; +import java.net.URL; + +import timber.log.Timber; + +import static com.mapbox.mapboxsdk.style.expressions.Expression.get; +import static com.mapbox.mapboxsdk.style.expressions.Expression.interpolate; +import static com.mapbox.mapboxsdk.style.expressions.Expression.linear; +import static com.mapbox.mapboxsdk.style.expressions.Expression.literal; +import static com.mapbox.mapboxsdk.style.expressions.Expression.rgb; +import static com.mapbox.mapboxsdk.style.expressions.Expression.rgba; +import static com.mapbox.mapboxsdk.style.expressions.Expression.stop; +import static com.mapbox.mapboxsdk.style.expressions.Expression.zoom; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleOpacity; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeColor; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleStrokeWidth; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapIntensity; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapOpacity; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapRadius; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.heatmapWeight; + +/** + * Test activity showcasing the heatmap layer api. + */ +public class HeatmapLayerActivity extends AppCompatActivity { + + private static final String EARTHQUAKE_SOURCE_URL = "https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"; + private static final String EARTHQUAKE_SOURCE_ID = "earthquakes"; + private static final String HEATMAP_LAYER_ID = "earthquakes-heat"; + private static final String HEATMAP_LAYER_SOURCE = "earthquakes"; + private static final String CIRCLE_LAYER_ID = "earthquakes-circle"; + + private MapView mapView; + private MapboxMap mapboxMap; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_heatmaplayer); + + mapView = (MapView) findViewById(R.id.mapView); + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(map -> { + mapboxMap = map; + addEarthquakeSource(); + addHeatmapLayer(); + addCircleLayer(); + }); + } + + private void addEarthquakeSource() { + try { + mapboxMap.addSource(new GeoJsonSource(EARTHQUAKE_SOURCE_ID, new URL(EARTHQUAKE_SOURCE_URL))); + } catch (MalformedURLException malformedUrlException) { + Timber.e(malformedUrlException, "That's not an url... "); + } + } + + private void addHeatmapLayer() { + HeatmapLayer layer = new HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID); + layer.setMaxZoom(9); + layer.setSourceLayer(HEATMAP_LAYER_SOURCE); + layer.setProperties( + + // TODO add heatmap color https://github.com/mapbox/mapbox-gl-native/issues/11172 + // Color ramp for heatmap. Domain is 0 (low) to 1 (high). + // Begin color ramp at 0-stop with a 0-transparancy color + // to create a blur-like effect. + //heatmapColor(), + + // Increase the heatmap weight based on frequency and property magnitude + heatmapWeight( + interpolate( + linear(), get("mag"), + stop(0, 0), + stop(6, 1) + ) + ), + + // Increase the heatmap color weight weight by zoom level + // heatmap-intensity is a multiplier on top of heatmap-weight + heatmapIntensity( + interpolate( + linear(), zoom(), + stop(0, 1), + stop(9, 3) + ) + ), + + // Adjust the heatmap radius by zoom level + heatmapRadius( + interpolate( + linear(), zoom(), + stop(0, 2), + stop(9, 20) + ) + ), + + // Transition from heatmap to circle layer by zoom level + heatmapOpacity( + interpolate( + linear(), zoom(), + stop(7, 1), + stop(9, 0) + ) + ) + ); + + mapboxMap.addLayerAbove(layer, "waterway-label"); + } + + private void addCircleLayer() { + CircleLayer circleLayer = new CircleLayer(CIRCLE_LAYER_ID, EARTHQUAKE_SOURCE_ID); + circleLayer.setProperties( + + // Size circle radius by earthquake magnitude and zoom level + circleRadius( + interpolate( + linear(), zoom(), + literal(7), interpolate( + linear(), get("mag"), + stop(1, 1), + stop(6, 4) + ), + literal(16), interpolate( + linear(), get("mag"), + stop(1, 5), + stop(6, 50) + ) + ) + ), + + // Color circle by earthquake magnitude + circleColor( + interpolate( + linear(), get("mag"), + literal(1), rgba(33, 102, 172, 0), + literal(2), rgb(103, 169, 207), + literal(3), rgb(209, 229, 240), + literal(4), rgb(253, 219, 199), + literal(5), rgb(239, 138, 98), + literal(6), rgb(178, 24, 43) + ) + ), + + // Transition from heatmap to circle layer by zoom level + circleOpacity( + interpolate( + linear(), zoom(), + stop(7, 0), + stop(8, 1) + ) + ), + circleStrokeColor("white"), + circleStrokeWidth(1.0f) + ); + + mapboxMap.addLayerBelow(circleLayer, HEATMAP_LAYER_ID); + } + + @Override + protected void onStart() { + super.onStart(); + mapView.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + mapView.onStop(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml new file mode 100644 index 0000000000..23ef9ea336 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <com.mapbox.mapboxsdk.maps.MapView + android:id="@id/mapView" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:mapbox_styleUrl="@string/mapbox_style_dark"/> + +</RelativeLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 51182f958c..fc04851293 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 @@ <string name="description_grid_source">Example Custom Geometry Source</string> <string name="description_local_glyph">Suzhou using Droid Sans for Chinese glyphs</string> <string name="description_hillshade">Example raster-dem source and hillshade layer</string> + <string name="description_heatmaplayer">Use HeatmapLayer to visualise earthquakes</string> </resources> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 078ccb64a9..e2bbf5af01 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 @@ <string name="activity_grid_source">Grid Source</string> <string name="activity_local_glyph">Local CJK glyph generation</string> <string name="activity_hillshade">Hillshade</string> + <string name="activity_heatmaplayer">Heatmap layer</string> </resources>
\ No newline at end of file diff --git a/platform/android/config.cmake b/platform/android/config.cmake index e1c36789f5..30182bbc06 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -158,6 +158,8 @@ add_library(mbgl-android STATIC platform/android/src/style/layers/fill_extrusion_layer.hpp platform/android/src/style/layers/fill_layer.cpp platform/android/src/style/layers/fill_layer.hpp + platform/android/src/style/layers/heatmap_layer.cpp + platform/android/src/style/layers/heatmap_layer.hpp platform/android/src/style/layers/hillshade_layer.cpp platform/android/src/style/layers/hillshade_layer.hpp platform/android/src/style/layers/layer.cpp diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json index 2170c4d9f8..eff7fb9cbf 100644 --- a/platform/android/scripts/exclude-activity-gen.json +++ b/platform/android/scripts/exclude-activity-gen.json @@ -34,4 +34,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 65b0a399ac..6e6d3cfa67 100755 --- a/platform/android/scripts/generate-style-code.js +++ b/platform/android/scripts/generate-style-code.js @@ -28,6 +28,9 @@ var layers = Object.keys(spec.layer.type.values).map((type) => { }, []); const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => { + // disabled for now, see https://github.com/mapbox/mapbox-gl-native/issues/11172 + if (name === 'heatmap-color') return memo; + spec[`paint_${type}`][name].name = name; memo.push(spec[`paint_${type}`][name]); return memo; diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp index afdb08a10e..2a03d9de9e 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<UpdateParameters> params) { mapRenderer.requestRender(); } -void AndroidRendererFrontend::onLowMemory() { - mapRenderer.actor().invoke(&Renderer::onLowMemory); +void AndroidRendererFrontend::reduceMemoryUse() { + mapRenderer.actor().invoke(&Renderer::reduceMemoryUse); } std::vector<Feature> 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 178870c452..b61904e388 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 1ed68d0835..6d0bd4de4b 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<ExampleCustomLayer*>(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<jlong>(nativeDenitialize)); + reinterpret_cast<jlong>(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 eefb578f39..3bd46631a0 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -450,7 +450,7 @@ jni::Array<jni::jlong> NativeMapView::addMarkers(jni::JNIEnv& env, jni::Array<jn } void NativeMapView::onLowMemory(JNIEnv&) { - rendererFrontend->onLowMemory(); + 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 0000000000..609499ec93 --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.cpp @@ -0,0 +1,134 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +#include "heatmap_layer.hpp" + +#include <string> + +#include "../conversion/property_value.hpp" +#include "../conversion/transition_options.hpp" + +namespace mbgl { +namespace android { + + /** + * Creates an owning peer object (for layers not attached to the map) from the JVM side + */ + HeatmapLayer::HeatmapLayer(jni::JNIEnv& env, jni::String layerId, jni::String sourceId) + : Layer(env, std::make_unique<mbgl::style::HeatmapLayer>(jni::Make<std::string>(env, layerId), jni::Make<std::string>(env, sourceId))) { + } + + /** + * Creates a non-owning peer object (for layers currently attached to the map) + */ + HeatmapLayer::HeatmapLayer(mbgl::Map& map, mbgl::style::HeatmapLayer& coreLayer) + : Layer(map, coreLayer) { + } + + /** + * Creates an owning peer object (for layers not attached to the map) + */ + HeatmapLayer::HeatmapLayer(mbgl::Map& map, std::unique_ptr<mbgl::style::HeatmapLayer> coreLayer) + : Layer(map, std::move(coreLayer)) { + } + + HeatmapLayer::~HeatmapLayer() = default; + + // Property getters + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapRadius(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapRadius()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<TransitionOptions> HeatmapLayer::getHeatmapRadiusTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapRadiusTransition(); + return *convert<jni::Object<TransitionOptions>>(env, options); + } + + void HeatmapLayer::setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapRadiusTransition(options); + } + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapWeight(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapWeight()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapIntensity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapIntensity()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<TransitionOptions> HeatmapLayer::getHeatmapIntensityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapIntensityTransition(); + return *convert<jni::Object<TransitionOptions>>(env, options); + } + + void HeatmapLayer::setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapIntensityTransition(options); + } + + jni::Object<jni::ObjectTag> HeatmapLayer::getHeatmapOpacity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapOpacity()); + return jni::Object<jni::ObjectTag>(*converted); + } + + jni::Object<TransitionOptions> HeatmapLayer::getHeatmapOpacityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::getHeatmapOpacityTransition(); + return *convert<jni::Object<TransitionOptions>>(env, options); + } + + void HeatmapLayer::setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as<mbgl::style::HeatmapLayer>()->HeatmapLayer::setHeatmapOpacityTransition(options); + } + + + jni::Class<HeatmapLayer> HeatmapLayer::javaClass; + + jni::jobject* HeatmapLayer::createJavaPeer(jni::JNIEnv& env) { + static auto constructor = HeatmapLayer::javaClass.template GetConstructor<jni::jlong>(env); + return HeatmapLayer::javaClass.New(env, constructor, reinterpret_cast<jni::jlong>(this)); + } + + void HeatmapLayer::registerNative(jni::JNIEnv& env) { + // Lookup the class + HeatmapLayer::javaClass = *jni::Class<HeatmapLayer>::Find(env).NewGlobalRef(env).release(); + + #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name) + + // Register the peer + jni::RegisterNativePeer<HeatmapLayer>( + env, HeatmapLayer::javaClass, "nativePtr", + std::make_unique<HeatmapLayer, JNIEnv&, jni::String, jni::String>, + "initialize", + "finalize", + METHOD(&HeatmapLayer::getHeatmapRadiusTransition, "nativeGetHeatmapRadiusTransition"), + METHOD(&HeatmapLayer::setHeatmapRadiusTransition, "nativeSetHeatmapRadiusTransition"), + METHOD(&HeatmapLayer::getHeatmapRadius, "nativeGetHeatmapRadius"), + METHOD(&HeatmapLayer::getHeatmapWeight, "nativeGetHeatmapWeight"), + METHOD(&HeatmapLayer::getHeatmapIntensityTransition, "nativeGetHeatmapIntensityTransition"), + METHOD(&HeatmapLayer::setHeatmapIntensityTransition, "nativeSetHeatmapIntensityTransition"), + METHOD(&HeatmapLayer::getHeatmapIntensity, "nativeGetHeatmapIntensity"), + METHOD(&HeatmapLayer::getHeatmapOpacityTransition, "nativeGetHeatmapOpacityTransition"), + METHOD(&HeatmapLayer::setHeatmapOpacityTransition, "nativeSetHeatmapOpacityTransition"), + METHOD(&HeatmapLayer::getHeatmapOpacity, "nativeGetHeatmapOpacity")); + } + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/layers/heatmap_layer.hpp b/platform/android/src/style/layers/heatmap_layer.hpp new file mode 100644 index 0000000000..85f9f0292e --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.hpp @@ -0,0 +1,50 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +#pragma once + +#include "layer.hpp" +#include "../transition_options.hpp" +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <jni/jni.hpp> + +namespace mbgl { +namespace android { + +class HeatmapLayer : public Layer { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/layers/HeatmapLayer"; }; + + static jni::Class<HeatmapLayer> javaClass; + + static void registerNative(jni::JNIEnv&); + + HeatmapLayer(jni::JNIEnv&, jni::String, jni::String); + + HeatmapLayer(mbgl::Map&, mbgl::style::HeatmapLayer&); + + HeatmapLayer(mbgl::Map&, std::unique_ptr<mbgl::style::HeatmapLayer>); + + ~HeatmapLayer(); + + // Properties + + jni::Object<jni::ObjectTag> getHeatmapRadius(jni::JNIEnv&); + void setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object<TransitionOptions> getHeatmapRadiusTransition(jni::JNIEnv&); + + jni::Object<jni::ObjectTag> getHeatmapWeight(jni::JNIEnv&); + + jni::Object<jni::ObjectTag> getHeatmapIntensity(jni::JNIEnv&); + void setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object<TransitionOptions> getHeatmapIntensityTransition(jni::JNIEnv&); + + jni::Object<jni::ObjectTag> getHeatmapOpacity(jni::JNIEnv&); + void setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object<TransitionOptions> getHeatmapOpacityTransition(jni::JNIEnv&); + jni::jobject* createJavaPeer(jni::JNIEnv&); + +}; // class HeatmapLayer + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp index da1550bdb1..29530879a5 100644 --- a/platform/android/src/style/layers/layer.cpp +++ b/platform/android/src/style/layers/layer.cpp @@ -10,6 +10,7 @@ #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> diff --git a/platform/android/src/style/layers/layers.cpp b/platform/android/src/style/layers/layers.cpp index 5d1d1bbcbf..5df689b45d 100644 --- a/platform/android/src/style/layers/layers.cpp +++ b/platform/android/src/style/layers/layers.cpp @@ -5,6 +5,7 @@ #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> @@ -16,6 +17,7 @@ #include "custom_layer.hpp" #include "fill_extrusion_layer.hpp" #include "fill_layer.hpp" +#include "heatmap_layer.hpp" #include "hillshade_layer.hpp" #include "line_layer.hpp" #include "raster_layer.hpp" @@ -32,6 +34,7 @@ template <> struct PeerType<style::BackgroundLayer> { using Type = android::Back template <> struct PeerType<style::CircleLayer> { using Type = android::CircleLayer; }; template <> struct PeerType<style::FillExtrusionLayer> { using Type = android::FillExtrusionLayer; }; template <> struct PeerType<style::FillLayer> { using Type = android::FillLayer; }; +template <> struct PeerType<style::HeatmapLayer> { using Type = android::HeatmapLayer; }; template <> struct PeerType<style::HillshadeLayer> { using Type = android::HillshadeLayer; }; template <> struct PeerType<style::LineLayer> { using Type = android::LineLayer; }; template <> struct PeerType<style::RasterLayer> { using Type = android::RasterLayer; }; @@ -95,6 +98,7 @@ void registerNativeLayers(jni::JNIEnv& env) { CustomLayer::registerNative(env); FillExtrusionLayer::registerNative(env); FillLayer::registerNative(env); + HeatmapLayer::registerNative(env); HillshadeLayer::registerNative(env); LineLayer::registerNative(env); RasterLayer::registerNative(env); diff --git a/platform/android/src/style/sources/custom_geometry_source.cpp b/platform/android/src/style/sources/custom_geometry_source.cpp index a60b962e45..b38405a3b1 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 fc259e4da3..37af150ac5 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 f919a649cb..8e82a8eb63 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 12cfa31575..b0c50a06f8 100644 --- a/platform/darwin/scripts/style-spec-overrides-v8.json +++ b/platform/darwin/scripts/style-spec-overrides-v8.json @@ -23,6 +23,9 @@ "circle": { "doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use many-sided regular polygons and configure their appearance using an `MGLFillStyleLayer` object." }, + "heatmap": { + "doc": "An `MGLHeatmapStyleLayer` is a style layer that renders a <a href=\"https://en.wikipedia.org/wiki/Heat_map\">heatmap</a>.\n\nA heatmap visualizes the spatial distribution of a large, dense set of point data, using color to avoid cluttering the map with individual points at low zoom levels. The points are weighted by an attribute you specify. Use a heatmap style layer in conjunction with point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nConsider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can alternatively cluster the source using the `MGLShapeSourceOptionClustered` option and render the data using an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer`." + }, "raster": { "doc": "An `MGLRasterStyleLayer` is a style layer that renders georeferenced raster imagery on the map, especially raster tiles.\n\nUse a raster style layer to configure the color parameters of raster tiles loaded by an `MGLRasterSource` object or raster images loaded by an `MGLImageSource` object. For example, you could use a raster style layer to render <a href=\"https://www.mapbox.com/satellite/\">Mapbox Satellite</a> imagery, a <a href=\"https://www.mapbox.com/help/define-tileset/#raster-tilesets\">raster tile set</a> uploaded to Mapbox Studio, or a raster map authored in <a href=\"https://tilemill-project.github.io/tilemill/\">TileMill</a>, the classic Mapbox Editor, or Mapbox Studio Classic.\n\nRaster images may also be used as icons or patterns in a style layer. To register an image for use as an icon or pattern, use the `-[MGLStyle setImage:forName:]` method. To configure a point annotation’s image, use the `MGLAnnotationImage` class." }, @@ -79,6 +82,11 @@ "doc": "The base color of this layer. The extrusion's surfaces will be shaded differently based on this color in combination with the `light` settings. If this color is specified with an alpha component, the alpha component will be ignored; use `fill-extrusion-opacity` to set layer opacityco." } }, + "paint_heatmap": { + "heatmap-color": { + "doc": "Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `$heatmapDensity` as input." + } + }, "paint_line": { "line-pattern": { "doc": "Name of image in style images to use for drawing image lines. For seamless patterns, image width must be a factor of two (2, 4, 8, ..., 512)." @@ -114,4 +122,4 @@ "doc": "Distance that the text's anchor is moved from its original placement." } } -}
\ No newline at end of file +} diff --git a/platform/darwin/src/MGLAbstractShapeSource.h b/platform/darwin/src/MGLAbstractShapeSource.h index 3b35986b3f..d61f41fbf6 100644 --- a/platform/darwin/src/MGLAbstractShapeSource.h +++ b/platform/darwin/src/MGLAbstractShapeSource.h @@ -82,6 +82,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 `MGLStyle` object along with an `MGLVectorStyleLayer` object. The vector style diff --git a/platform/darwin/src/MGLAbstractShapeSource.mm b/platform/darwin/src/MGLAbstractShapeSource.mm index 755d040387..eb0807cee0 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 0000000000..ff2b92f226 --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.h @@ -0,0 +1,189 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLFoundation.h" +#import "MGLVectorStyleLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + An `MGLHeatmapStyleLayer` is a style layer that renders a <a + href="https://en.wikipedia.org/wiki/Heat_map">heatmap</a>. + + A heatmap visualizes the spatial distribution of a large, dense set of point + data, using color to avoid cluttering the map with individual points at low + zoom levels. The points are weighted by an attribute you specify. Use a heatmap + style layer in conjunction with point or point collection features in vector + tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, + `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` + instances in an `MGLShapeSource` object. + + Consider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or + `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point + data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can + alternatively cluster the source using the `MGLShapeSourceOptionClustered` + option and render the data using an `MGLCircleStyleLayer` or + `MGLSymbolStyleLayer`. + + You can access an existing heatmap style layer using the + `-[MGLStyle layerWithIdentifier:]` method if you know its identifier; + otherwise, find it using the `MGLStyle.layers` property. You can also create a + new heatmap style layer and add it to the style using a method such as + `-[MGLStyle addLayer:]`. + + ### Example + + ```swift + let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) + layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 0, + 6: 1]) + layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 1, + 9: 3]) + mapView.style?.addLayer(layer) + ``` + */ +MGL_EXPORT +@interface MGLHeatmapStyleLayer : MGLVectorStyleLayer + +/** + Returns a heatmap style layer initialized with an identifier and source. + + After initializing and configuring the style layer, add it to a map view’s + style using the `-[MGLStyle addLayer:]` or + `-[MGLStyle insertLayer:belowLayer:]` method. + + @param identifier A string that uniquely identifies the source in the style to + which it is added. + @param source The source from which to obtain the data to style. If the source + has not yet been added to the current style, the behavior is undefined. + @return An initialized foreground style layer. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source; + +#pragma mark - Accessing the Paint Attributes + +/** + Defines the color of each point based on its density value in a heatmap. Should + be an expression that uses `$heatmapDensity` as input. + + The default value of this property is an expression that evaluates to a rainbow + color scale from blue to red. Set this property to `nil` to reset it to the + default value. + + You can set this property to an expression containing any of the following: + + * Constant `UIColor` values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$heatmapDensity` variable + + This property does not support applying interpolation or step functions to + feature attributes. + */ +@property (nonatomic, null_resettable) NSExpression *heatmapColor; + +/** + Similar to `heatmapWeight` but controls the intensity of the heatmap globally. + Primarily used for adjusting the heatmap based on zoom level. + + The default value of this property is an expression that evaluates to the float + 1. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values no less than 0 + * 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 between 0 and 1 inclusive + * 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 no less than 1 + * 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 no less than 0 + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable and/or + feature attributes + */ +@property (nonatomic, null_resettable) NSExpression *heatmapWeight; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.mm b/platform/darwin/src/MGLHeatmapStyleLayer.mm new file mode 100644 index 0000000000..a394dbda3b --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.mm @@ -0,0 +1,210 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLSource.h" +#import "NSPredicate+MGLAdditions.h" +#import "NSDate+MGLAdditions.h" +#import "MGLStyleLayer_Private.h" +#import "MGLStyleValue_Private.h" +#import "MGLHeatmapStyleLayer.h" + +#include <mbgl/style/transition_options.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> + +@interface MGLHeatmapStyleLayer () + +@property (nonatomic, readonly) mbgl::style::HeatmapLayer *rawLayer; + +@end + +@implementation MGLHeatmapStyleLayer + +- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source +{ + auto layer = std::make_unique<mbgl::style::HeatmapLayer>(identifier.UTF8String, source.identifier.UTF8String); + return self = [super initWithPendingLayer:std::move(layer)]; +} + +- (mbgl::style::HeatmapLayer *)rawLayer +{ + return (mbgl::style::HeatmapLayer *)super.rawLayer; +} + +- (NSString *)sourceIdentifier +{ + MGLAssertStyleLayerIsValid(); + + return @(self.rawLayer->getSourceID().c_str()); +} + +- (NSString *)sourceLayerIdentifier +{ + MGLAssertStyleLayerIsValid(); + + auto layerID = self.rawLayer->getSourceLayer(); + return layerID.empty() ? nil : @(layerID.c_str()); +} + +- (void)setSourceLayerIdentifier:(NSString *)sourceLayerIdentifier +{ + MGLAssertStyleLayerIsValid(); + + self.rawLayer->setSourceLayer(sourceLayerIdentifier.UTF8String ?: ""); +} + +- (void)setPredicate:(NSPredicate *)predicate +{ + MGLAssertStyleLayerIsValid(); + + self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter()); +} + +- (NSPredicate *)predicate +{ + MGLAssertStyleLayerIsValid(); + + return [NSPredicate mgl_predicateWithFilter:self.rawLayer->getFilter()]; +} + +#pragma mark - Accessing the Paint Attributes + +- (void)setHeatmapColor:(NSExpression *)heatmapColor { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toPropertyValue<mbgl::style::HeatmapColorPropertyValue>(heatmapColor); + self.rawLayer->setHeatmapColor(mbglValue); +} + +- (NSExpression *)heatmapColor { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapColor(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapColor(); + } + return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toExpression(propertyValue); +} + +- (void)setHeatmapIntensity:(NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::PropertyValue<float>>(heatmapIntensity); + self.rawLayer->setHeatmapIntensity(mbglValue); +} + +- (NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapIntensity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapIntensity(); + } + return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue); +} + +- (void)setHeatmapIntensityTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapIntensityTransition(options); +} + +- (MGLTransition)heatmapIntensityTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapIntensityTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapOpacity:(NSExpression *)heatmapOpacity { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::PropertyValue<float>>(heatmapOpacity); + self.rawLayer->setHeatmapOpacity(mbglValue); +} + +- (NSExpression *)heatmapOpacity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapOpacity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapOpacity(); + } + return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue); +} + +- (void)setHeatmapOpacityTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapOpacityTransition(options); +} + +- (MGLTransition)heatmapOpacityTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapOpacityTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapRadius:(NSExpression *)heatmapRadius { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<float>>(heatmapRadius); + self.rawLayer->setHeatmapRadius(mbglValue); +} + +- (NSExpression *)heatmapRadius { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapRadius(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapRadius(); + } + return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue); +} + +- (void)setHeatmapRadiusTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapRadiusTransition(options); +} + +- (MGLTransition)heatmapRadiusTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapRadiusTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapWeight:(NSExpression *)heatmapWeight { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<float>>(heatmapWeight); + self.rawLayer->setHeatmapWeight(mbglValue); +} + +- (NSExpression *)heatmapWeight { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapWeight(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapWeight(); + } + return MGLStyleValueTransformer<float, NSNumber *>().toExpression(propertyValue); +} + +@end diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index f03bd70c86..11a5442761 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<mbgl::MapSnapshotter> _mbglMapSnapshotter; std::unique_ptr<mbgl::Actor<mbgl::MapSnapshotter::Callback>> _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::Actor<mbgl::MapSnapshotter::Callback>>(*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::Actor<mbgl::MapSnapshotter::Callback>>(*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 { + [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 - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:self.options.scale]; + CGFloat fontSize = [UIFont smallSystemFontSize]; + UIColor *attributeFontColor = [UIColor blackColor]; #else - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; - mglImage.size = NSMakeSize(mglImage.size.width / self.options.scale, - mglImage.size.height / self.options.scale); + CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize]; + NSColor *attributeFontColor = [NSColor blackColor]; #endif - - // Process image watermark in a work queue - dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(workQueue, ^{ + 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(); + 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); - }); - }); + 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()); }); + return nil; } - (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin diff --git a/platform/darwin/src/MGLRendererFrontend.h b/platform/darwin/src/MGLRendererFrontend.h index 76904d008b..2df67ca4e4 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 08d1ac6735..3bf038235e 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -8,6 +8,7 @@ #import "MGLLineStyleLayer.h" #import "MGLCircleStyleLayer.h" #import "MGLSymbolStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLRasterStyleLayer.h" #import "MGLBackgroundStyleLayer.h" @@ -36,6 +37,7 @@ #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/background_layer.hpp> @@ -352,6 +354,8 @@ static_assert(6 == mbgl::util::default_styles::numOrderedStyles, return [[MGLSymbolStyleLayer alloc] initWithRawLayer:symbolLayer]; } else if (auto rasterLayer = rawLayer->as<mbgl::style::RasterLayer>()) { return [[MGLRasterStyleLayer alloc] initWithRawLayer:rasterLayer]; + } else if (auto heatmapLayer = rawLayer->as<mbgl::style::HeatmapLayer>()) { + return [[MGLHeatmapStyleLayer alloc] initWithRawLayer:heatmapLayer]; } else if (auto hillshadeLayer = rawLayer->as<mbgl::style::HillshadeLayer>()) { return [[MGLHillshadeStyleLayer alloc] initWithRawLayer:hillshadeLayer]; } else if (auto circleLayer = rawLayer->as<mbgl::style::CircleLayer>()) { diff --git a/platform/darwin/src/MGLStyleLayer.mm.ejs b/platform/darwin/src/MGLStyleLayer.mm.ejs index 41b029791f..ac7676a1cc 100644 --- a/platform/darwin/src/MGLStyleLayer.mm.ejs +++ b/platform/darwin/src/MGLStyleLayer.mm.ejs @@ -157,7 +157,9 @@ namespace mbgl { - (void)set<%- camelize(property.name) %>:(NSExpression *)<%- objCName(property) %> { MGLAssertStyleLayerIsValid(); -<% if (property["property-function"]) { -%> +<% if (property.name === 'heatmap-color') { -%> + auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toPropertyValue<mbgl::style::HeatmapColorPropertyValue>(heatmapColor); +<% } else if (property["property-function"]) { -%> auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue<mbgl::style::DataDrivenPropertyValue<<%- valueTransformerArguments(property)[0] %>>>(<%- objCName(property) %>); <% } else { -%> auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue<mbgl::style::PropertyValue<<%- valueTransformerArguments(property)[0] %>>>(<%- objCName(property) %>); diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h index ba4b413a3c..5124c29a90 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -10,6 +10,7 @@ #import "MGLConversion.h" #include <mbgl/style/conversion/property_value.hpp> #include <mbgl/style/conversion/data_driven_property_value.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> #include <mbgl/style/conversion/position.hpp> #import <mbgl/style/types.hpp> @@ -52,11 +53,20 @@ public: return mbglValue.evaluate(evaluator); } + // Convert an mbgl heatmap color property value into an mgl style value + NSExpression *toExpression(const mbgl::style::HeatmapColorPropertyValue &mbglValue) { + if (mbglValue.isUndefined()) { + return nil; + } + return [NSExpression mgl_expressionWithJSONObject:MGLJSONObjectFromMBGLExpression(mbglValue.getExpression())]; + } + /** Converts an NSExpression to an mbgl property value. */ template <typename MBGLValue> - MBGLValue toPropertyValue(NSExpression *expression) { + typename std::enable_if_t<!std::is_same<MBGLValue, mbgl::style::HeatmapColorPropertyValue>::value, + MBGLValue> toPropertyValue(NSExpression *expression) { if (!expression) { return {}; } @@ -85,6 +95,30 @@ public: return *value; } + + /** + Converts an NSExpression to an mbgl property value. + */ + template <typename MBGLValue> + typename std::enable_if_t<std::is_same<MBGLValue, mbgl::style::HeatmapColorPropertyValue>::value, + MBGLValue> toPropertyValue(NSExpression *expression) { + if (!expression) { + return {}; + } + + NSArray *jsonExpression = expression.mgl_jsonExpressionObject; + + mbgl::style::conversion::Error valueError; + auto value = mbgl::style::conversion::convert<mbgl::style::HeatmapColorPropertyValue>( + mbgl::style::conversion::makeConvertible(jsonExpression), valueError); + if (!value) { + [NSException raise:NSInvalidArgumentException + format:@"Invalid property value: %@", @(valueError.message.c_str())]; + return {}; + } + + return *value; + } private: // Private utilities for converting from mgl to mbgl values diff --git a/platform/darwin/src/MGLTileSource.h b/platform/darwin/src/MGLTileSource.h index caeafcd2f6..3dc268db60 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 + <a href="https://github.com/mapbox/tilejson-spec/tree/master/2.1.0">TileJSON</a> + 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 5644ad9a06..bc985bd728 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 <UIKit/UIKit.h> @@ -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 7780a34c7f..177b1b70f0 100644 --- a/platform/darwin/src/MGLVectorStyleLayer.h +++ b/platform/darwin/src/MGLVectorStyleLayer.h @@ -10,10 +10,10 @@ NS_ASSUME_NONNULL_BEGIN is defined by an `MGLShapeSource` or `MGLVectorSource` object. Create instances of `MGLCircleStyleLayer`, `MGLFillStyleLayer`, - `MGLFillExtrusionStyleLayer`, `MGLLineStyleLayer`, and `MGLSymbolStyleLayer` in - order to use `MGLVectorStyleLayer`'s properties and methods. Do not create - instances of `MGLVectorStyleLayer` directly, and do not create your own - subclasses of this class. + `MGLFillExtrusionStyleLayer`, `MGLHeatmapStyleLayer`, `MGLLineStyleLayer`, and + `MGLSymbolStyleLayer` in order to use `MGLVectorStyleLayer`'s properties and + methods. Do not create instances of `MGLVectorStyleLayer` directly, and do not + create your own subclasses of this class. */ MGL_EXPORT @interface MGLVectorStyleLayer : MGLForegroundStyleLayer diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 9bf9924869..5a6e00bc4e 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -230,6 +230,24 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.layer(withIdentifier: "buildings")) } + + func testMGLHeatmapStyleLayer() { + let earthquakes = MGLShapeSource(identifier: "earthquakes", url: URL(string: "https://example.com/earthquakes.json")!, options: [:]) + mapView.style?.addSource(earthquakes) + + //#-example-code + let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) + layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 0, + 6: 1]) + layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 1, + 9: 3]) + mapView.style?.addLayer(layer) + //#-end-example-code + + XCTAssertNotNil(mapView.style?.layer(withIdentifier: "earthquake-heat")) + } func testMGLSymbolStyleLayer() { let pois = MGLVectorSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!) diff --git a/platform/darwin/test/MGLHeatmapColorTests.mm b/platform/darwin/test/MGLHeatmapColorTests.mm new file mode 100644 index 0000000000..8d44064d94 --- /dev/null +++ b/platform/darwin/test/MGLHeatmapColorTests.mm @@ -0,0 +1,61 @@ +#import <Mapbox/Mapbox.h> +#import <XCTest/XCTest.h> + +#import "MGLStyleLayer_Private.h" + +#include <mbgl/style/layers/heatmap_layer.hpp> + +@interface MGLHeatmapColorTests : XCTestCase <MGLMapViewDelegate> +@end + +@implementation MGLHeatmapColorTests + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>(); + + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"heatmap-color should be unset initially."); + NSExpression *defaultExpression = layer.heatmapColor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.heatmapColor = constantExpression; + + + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), + @"Setting heatmapColor to a constant value expression should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, constantExpression, + @"heatmapColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MGLColor blueColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($heatmapDensity, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@12: constantExpression2}]; + layer.heatmapColor = functionExpression; + + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, functionExpression, + @"heatmapColor should round-trip expressions depending on $heatmapDensity."); + + layer.heatmapColor = nil; + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"Unsetting heatmapColor should return heatmap-color to the default value."); + XCTAssertEqualObjects(layer.heatmapColor, defaultExpression, + @"heatmapColor should return the default value after being unset."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a data expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); +} + +@end diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm new file mode 100644 index 0000000000..74121affd8 --- /dev/null +++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm @@ -0,0 +1,296 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLStyleLayerTests.h" +#import "../../darwin/src/NSDate+MGLAdditions.h" + +#import "MGLStyleLayer_Private.h" + +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/transition_options.hpp> + +@interface MGLHeatmapLayerTests : MGLStyleLayerTests +@end + +@implementation MGLHeatmapLayerTests + ++ (NSString *)layerType { + return @"heatmap"; +} + +- (void)testPredicates { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + XCTAssertNil(layer.sourceLayerIdentifier); + layer.sourceLayerIdentifier = @"layerID"; + XCTAssertEqualObjects(layer.sourceLayerIdentifier, @"layerID"); + layer.sourceLayerIdentifier = nil; + XCTAssertNil(layer.sourceLayerIdentifier); + + XCTAssertNil(layer.predicate); + layer.predicate = [NSPredicate predicateWithValue:NO]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = nil; + XCTAssertNil(layer.predicate); +} + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + XCTAssertNotEqual(layer.rawLayer, nullptr); + XCTAssertTrue(layer.rawLayer->is<mbgl::style::HeatmapLayer>()); + auto rawLayer = layer.rawLayer->as<mbgl::style::HeatmapLayer>(); + + MGLTransition transitionTest = MGLTransitionMake(5, 4); + + + // heatmap-intensity + { + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"heatmap-intensity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapIntensity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapIntensity = constantExpression; + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a constant value expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, constantExpression, + @"heatmapIntensity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapIntensity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a camera expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, functionExpression, + @"heatmapIntensity should round-trip camera expressions."); + + + + layer.heatmapIntensity = nil; + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"Unsetting heatmapIntensity should return heatmap-intensity to the default value."); + XCTAssertEqualObjects(layer.heatmapIntensity, defaultExpression, + @"heatmapIntensity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapIntensityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapIntensityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapIntensityTransition = layer.heatmapIntensityTransition; + XCTAssertEqual(heatmapIntensityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapIntensityTransition.duration, transitionTest.duration); + } + + // heatmap-opacity + { + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"heatmap-opacity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapOpacity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapOpacity = constantExpression; + mbgl::style::PropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a constant value expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, constantExpression, + @"heatmapOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapOpacity = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a camera expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, functionExpression, + @"heatmapOpacity should round-trip camera expressions."); + + + + layer.heatmapOpacity = nil; + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"Unsetting heatmapOpacity should return heatmap-opacity to the default value."); + XCTAssertEqualObjects(layer.heatmapOpacity, defaultExpression, + @"heatmapOpacity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapOpacityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapOpacityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapOpacityTransition = layer.heatmapOpacityTransition; + XCTAssertEqual(heatmapOpacityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapOpacityTransition.duration, transitionTest.duration); + } + + // heatmap-radius + { + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"heatmap-radius should be unset initially."); + NSExpression *defaultExpression = layer.heatmapRadius; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapRadius = constantExpression; + mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a constant value expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, constantExpression, + @"heatmapRadius should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a data expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapRadius = functionExpression; + + std::map<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera-data expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip camera-data expressions."); + + + layer.heatmapRadius = nil; + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"Unsetting heatmapRadius should return heatmap-radius to the default value."); + XCTAssertEqualObjects(layer.heatmapRadius, defaultExpression, + @"heatmapRadius should return the default value after being unset."); + // Transition property test + layer.heatmapRadiusTransition = transitionTest; + auto toptions = rawLayer->getHeatmapRadiusTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapRadiusTransition = layer.heatmapRadiusTransition; + XCTAssertEqual(heatmapRadiusTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapRadiusTransition.duration, transitionTest.duration); + } + + // heatmap-weight + { + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"heatmap-weight should be unset initially."); + NSExpression *defaultExpression = layer.heatmapWeight; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapWeight = constantExpression; + mbgl::style::DataDrivenPropertyValue<float> propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a constant value expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, constantExpression, + @"heatmapWeight should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::IntervalStops<float> intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction<float> { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::ExponentialStops<float> exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction<float> { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a data expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapWeight = functionExpression; + + std::map<float, float> innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops<float> compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction<float> { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera-data expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip camera-data expressions."); + + + layer.heatmapWeight = nil; + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"Unsetting heatmapWeight should return heatmap-weight to the default value."); + XCTAssertEqualObjects(layer.heatmapWeight, defaultExpression, + @"heatmapWeight should return the default value after being unset."); + } +} + +- (void)testPropertyNames { + [self testPropertyName:@"heatmap-intensity" isBoolean:NO]; + [self testPropertyName:@"heatmap-opacity" isBoolean:NO]; + [self testPropertyName:@"heatmap-radius" isBoolean:NO]; + [self testPropertyName:@"heatmap-weight" isBoolean:NO]; +} + +@end diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs index e26c63e662..e17501ed18 100644 --- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs @@ -59,6 +59,7 @@ MGLTransition transitionTest = MGLTransitionMake(5, 4); <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> // <%- originalPropertyName(property) %> { @@ -151,6 +152,7 @@ - (void)testPropertyNames { <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> [self testPropertyName:@"<%- property.getter || property.name %>" isBoolean:<%- property.type === 'boolean' ? 'YES' : 'NO' %>]; <% } -%> } diff --git a/platform/darwin/test/MGLTileSetTests.mm b/platform/darwin/test/MGLTileSetTests.mm index 40eab5f974..4d5e1fcd05 100644 --- a/platform/darwin/test/MGLTileSetTests.mm +++ b/platform/darwin/test/MGLTileSetTests.mm @@ -2,6 +2,7 @@ #import <Mapbox/Mapbox.h> #import "MGLTileSource_Private.h" +#import "MGLGeometry_Private.h" #include <mbgl/util/tileset.hpp> @@ -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 54dbb8d0f6..3063bf88a0 100644 --- a/platform/default/asset_file_source.cpp +++ b/platform/default/asset_file_source.cpp @@ -10,6 +10,12 @@ #include <sys/types.h> #include <sys/stat.h> +namespace { + +const std::string assetProtocol = "asset://"; + +} // namespace + namespace mbgl { class AssetFileSource::Impl { @@ -19,18 +25,17 @@ public: } void request(const std::string& url, ActorRef<FileSourceRequest> req) { - std::string path; + Response response; - 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)); + if (!acceptsURL(url)) { + response.error = std::make_unique<Response::Error>(Response::Error::Reason::Other, + "Invalid asset URL"); + req.invoke(&FileSourceRequest::setResponse, response); + return; } - Response response; - + // 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<AsyncRequest> 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 608b782ab9..cb602995a4 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -14,16 +14,6 @@ #include <cassert> -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 21803d0935..0635e86d80 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<Impl>) {} void request(const std::string& url, ActorRef<FileSourceRequest> 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>(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<AsyncRequest> 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 0c5599bac7..c11fc63ebd 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -12,6 +12,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)) @@ -35,7 +36,6 @@ 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 @@ -44,6 +44,11 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT * 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 3bf93d8721..95d3251e2e 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 c6d34d3f06..7c8e9fe570 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}, @@ -1885,7 +1886,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 8db1bd3310..02d23067ef 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 68071123af..f66aa74965 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 = "<group>"; }; 16376B401FFDB4B40000563E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewLayoutTests.m; sourceTree = "<group>"; }; + 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapColorTests.mm; path = ../../darwin/test/MGLHeatmapColorTests.mm; sourceTree = "<group>"; }; + 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapStyleLayerTests.mm; path = ../../darwin/test/MGLHeatmapStyleLayerTests.mm; sourceTree = "<group>"; }; 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; }; 1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = "<group>"; }; 1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = "<group>"; }; @@ -797,6 +811,8 @@ 632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXEmbeddedMapViewController.h; sourceTree = "<group>"; }; 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXEmbeddedMapViewController.m; sourceTree = "<group>"; }; 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationExampleTests.swift; path = ../../darwin/test/MGLDocumentationExampleTests.swift; sourceTree = "<group>"; }; + 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = "<group>"; }; + 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = "<group>"; }; 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = "<group>"; }; 927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXSnapshotsViewController.h; sourceTree = "<group>"; }; 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXSnapshotsViewController.m; sourceTree = "<group>"; }; @@ -831,6 +847,9 @@ 96E0272D1E57C7E6004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; }; 96E0272E1E57C7E7004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; }; 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = "<group>"; }; + AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = "<group>"; }; + AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = "<group>"; }; + CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; sourceTree = "<group>"; }; DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = "<group>"; }; DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = "<group>"; }; DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; }; @@ -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 */, + 35CE617F1D4165C2004F2359 /* Categories */, + DA88487F1CBB033F00AB86E3 /* Fabric */, + DA8848881CBB036000AB86E3 /* SMCalloutView */, DAD165851CF4D08B001FF4B9 /* Telemetry */, + 355ADFF91E9281C300F3939D /* Views */, + CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */, DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */, DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */, - DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */, + DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, + DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, - DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, - DA88487F1CBB033F00AB86E3 /* Fabric */, - DA8848881CBB036000AB86E3 /* SMCalloutView */, ); 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 = "<group>"; @@ -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 b3662adc70..61e9ad39e8 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -95,6 +95,7 @@ custom_categories: - MGLCircleStyleLayer - MGLFillStyleLayer - MGLFillExtrusionStyleLayer + - MGLHeatmapStyleLayer - MGLHillshadeStyleLayer - MGLLineStyleLayer - MGLSymbolStyleLayer diff --git a/platform/ios/src/MGLCameraChangeReason.h b/platform/ios/src/MGLCameraChangeReason.h new file mode 100644 index 0000000000..6c6b3636ba --- /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 b0d2e17d5d..85ef4ca489 100644 --- a/platform/ios/src/MGLLocationManager.m +++ b/platform/ios/src/MGLLocationManager.m @@ -1,9 +1,9 @@ #import "MGLLocationManager.h" +#import "MGLTelemetryConfig.h" #import <UIKit/UIKit.h> 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 63ec5cb987..72fa4613fc 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 }); } @@ -2856,6 +2900,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]; } @@ -2905,6 +2951,9 @@ public: } _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + _mbglMap->easeTo(cameraOptions, animationOptions); } @@ -2928,6 +2977,8 @@ public: if (zoomLevel == self.zoomLevel) return; _mbglMap->cancelTransitions(); + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + CGFloat duration = animated ? MGLAnimationDuration : 0; _mbglMap->setZoom(zoomLevel, @@ -3016,6 +3067,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]; } @@ -3065,6 +3119,9 @@ public: [self willChangeValueForKey:@"visibleCoordinateBounds"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"visibleCoordinateBounds"]; } @@ -3098,6 +3155,8 @@ public: CGFloat duration = animated ? MGLAnimationDuration : 0; + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + if (self.userTrackingMode == MGLUserTrackingModeNone) { _mbglMap->setBearing(direction, @@ -3182,6 +3241,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"]; @@ -3238,6 +3300,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"]; @@ -3398,6 +3463,13 @@ public: return [self metersPerPointAtLatitude:latitude]; } +#pragma mark - Camera Change Reason - + +- (void)resetCameraChangeReason +{ + self.cameraChangeReasonBitmask = MGLCameraChangeReasonNone; +} + #pragma mark - Styling - - (NS_ARRAY_OF(NSURL *) *)bundledStyleURLs @@ -5309,9 +5381,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]; + } } } @@ -5325,8 +5404,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]; } @@ -5339,9 +5422,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; @@ -5353,7 +5440,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 096711fcbb..0368d8413c 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -1,6 +1,7 @@ #import <UIKit/UIKit.h> #import "MGLTypes.h" +#import "MGLCameraChangeReason.h" NS_ASSUME_NONNULL_BEGIN @@ -22,17 +23,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. This method is called as the currently displayed map camera changes as part of @@ -49,6 +113,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 d59972f5bf..273af5b3bc 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 <mbgl/storage/reachability.h> #include <sys/sysctl.h> @@ -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 0000000000..527d344291 --- /dev/null +++ b/platform/ios/src/MGLTelemetryConfig.h @@ -0,0 +1,18 @@ +#import <Foundation/Foundation.h> +#import <CoreLocation/CoreLocation.h> + +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 0000000000..828bafb14f --- /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 d105189bb8..11720ac68e 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -45,6 +45,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLSymbolStyleLayer.h" #import "MGLRasterStyleLayer.h" #import "MGLCircleStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLBackgroundStyleLayer.h" #import "MGLOpenGLStyleLayer.h" diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 50f101e86b..4d11b000b9 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 4ed3d89399..ba15af918a 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 cd47fbb977..06fedf7cc7 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -4,16 +4,19 @@ ### 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)) diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json new file mode 100644 index 0000000000..10226ca23a --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "heatmap.pdf", + "language-direction" : "left-to-right" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +}
\ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf Binary files differnew file mode 100644 index 0000000000..88195c3735 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 3f6127ca27..03557caca2 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -720,8 +720,14 @@ NS_ARRAY_OF(id <MGLAnnotation>) *MBXFlattenedShapes(NS_ARRAY_OF(id <MGLAnnotatio [self.undoManager setActionName:@"Add Graticule"]; } + NSDictionary *sourceOptions = @{ + MGLShapeSourceOptionMaximumZoomLevel:@14, + MGLShapeSourceOptionWrapsCoordinates: @YES, + MGLShapeSourceOptionClipsCoordinates: @YES, + }; MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"graticule" - options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}]; + options:sourceOptions]; + source.dataSource = self; [self.mapView.style addSource:source]; MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"graticule.lines" diff --git a/platform/macos/app/StyleLayerIconTransformer.m b/platform/macos/app/StyleLayerIconTransformer.m index 50fe06a2c6..199de86d8b 100644 --- a/platform/macos/app/StyleLayerIconTransformer.m +++ b/platform/macos/app/StyleLayerIconTransformer.m @@ -31,6 +31,9 @@ if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) { return [NSImage imageNamed:@"symbol"]; } + if ([layer isKindOfClass:[MGLHeatmapStyleLayer class]]) { + return [NSImage imageNamed:@"heatmap"]; + } if ([layer isKindOfClass:[MGLHillshadeStyleLayer class]]) { return [NSImage imageNamed:@"hillshade"]; } diff --git a/platform/macos/app/heatmap.json b/platform/macos/app/heatmap.json new file mode 100644 index 0000000000..6469e57022 --- /dev/null +++ b/platform/macos/app/heatmap.json @@ -0,0 +1,809 @@ +{ + "version": 8, + "name": "Basic Heatmap", + "center": [ + 30.49860107152665, + 50.459868549177486 + ], + "zoom": 14.033276876197775, + "bearing": 0, + "pitch": 0, + "sources": { + "mapbox": { + "url": "mapbox://mapbox.mapbox-streets-v7", + "type": "vector" + } + }, + "sprite": "mapbox://sprites/mourner/cjcgg2bl16cf42snvcbbaf09z", + "glyphs": "mapbox://fonts/mourner/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "#dedede" + } + }, + { + "id": "landuse_overlay_national_park", + "type": "fill", + "source": "mapbox", + "source-layer": "landuse_overlay", + "filter": [ + "==", + "class", + "national_park" + ], + "paint": { + "fill-color": "#d2edae", + "fill-opacity": 0.75 + } + }, + { + "id": "landuse_park", + "type": "fill", + "source": "mapbox", + "source-layer": "landuse", + "filter": [ + "==", + "class", + "park" + ], + "paint": { + "fill-color": "#d2edae" + } + }, + { + "id": "waterway", + "type": "line", + "source": "mapbox", + "source-layer": "waterway", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "in", + "class", + "canal", + "river" + ] + ], + "paint": { + "line-color": "#a0cfdf", + "line-width": { + "base": 1.4, + "stops": [ + [ + 8, + 0.5 + ], + [ + 20, + 15 + ] + ] + } + } + }, + { + "id": "water", + "type": "fill", + "source": "mapbox", + "source-layer": "water", + "paint": { + "fill-color": "#a0cfdf" + } + }, + { + "id": "building", + "type": "fill", + "source": "mapbox", + "source-layer": "building", + "paint": { + "fill-color": "#d6d6d6" + } + }, + { + "id": "tunnel_minor", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "tunnel" + ], + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#efefef", + "line-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + }, + "line-dasharray": [ + 0.36, + 0.18 + ] + } + }, + { + "id": "tunnel_major", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "tunnel" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.4, + "stops": [ + [ + 6, + 0.5 + ], + [ + 20, + 30 + ] + ] + }, + "line-dasharray": [ + 0.28, + 0.14 + ] + } + }, + { + "id": "road_minor", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ], + [ + "in", + "structure", + "ford", + "none" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#efefef", + "line-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "road_major", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ], + [ + "in", + "structure", + "ford", + "none" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.4, + "stops": [ + [ + 6, + 0.5 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_minor case", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#dedede", + "line-width": { + "base": 1.6, + "stops": [ + [ + 12, + 0.5 + ], + [ + 20, + 10 + ] + ] + }, + "line-gap-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_major case", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ] + ], + "layout": { + "line-cap": "butt", + "line-join": "miter" + }, + "paint": { + "line-color": "#dedede", + "line-width": { + "base": 1.6, + "stops": [ + [ + 12, + 0.5 + ], + [ + 20, + 10 + ] + ] + }, + "line-gap-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_minor", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "link", + "motorway_link", + "path", + "pedestrian", + "service", + "street", + "street_limited", + "track" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#efefef", + "line-width": { + "base": 1.55, + "stops": [ + [ + 4, + 0.25 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "bridge_major", + "type": "line", + "source": "mapbox", + "source-layer": "road", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "==", + "structure", + "bridge" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#fff", + "line-width": { + "base": 1.4, + "stops": [ + [ + 6, + 0.5 + ], + [ + 20, + 30 + ] + ] + } + } + }, + { + "id": "admin_country", + "type": "line", + "source": "mapbox", + "source-layer": "admin", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "all", + [ + "<=", + "admin_level", + 2 + ], + [ + "==", + "maritime", + 0 + ] + ] + ], + "layout": { + "line-cap": "round", + "line-join": "round" + }, + "paint": { + "line-color": "#8b8a8a", + "line-width": { + "base": 1.3, + "stops": [ + [ + 3, + 0.5 + ], + [ + 22, + 15 + ] + ] + } + } + }, + { + "id": "road_major_label", + "type": "symbol", + "source": "mapbox", + "source-layer": "road_label", + "filter": [ + "all", + [ + "==", + "$type", + "LineString" + ], + [ + "in", + "class", + "motorway", + "primary", + "secondary", + "tertiary", + "trunk" + ] + ], + "layout": { + "symbol-placement": "line", + "text-field": "{name_en}", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-transform": "uppercase", + "text-letter-spacing": 0.1, + "text-size": { + "base": 1.4, + "stops": [ + [ + 10, + 8 + ], + [ + 20, + 14 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 2 + } + }, + { + "id": "place_label_other", + "type": "symbol", + "source": "mapbox", + "source-layer": "place_label", + "minzoom": 8, + "filter": [ + "all", + [ + "==", + "$type", + "Point" + ], + [ + "in", + "type", + "hamlet", + "island", + "neighbourhood", + "suburb", + "town", + "village" + ] + ], + "layout": { + "text-field": "{name_en}", + "text-font": [ + "Open Sans Semibold", + "Arial Unicode MS Bold" + ], + "text-max-width": 6, + "text-size": { + "stops": [ + [ + 6, + 12 + ], + [ + 12, + 16 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 1, + "text-halo-blur": 1 + } + }, + { + "id": "place_label_city", + "type": "symbol", + "source": "mapbox", + "source-layer": "place_label", + "maxzoom": 16, + "filter": [ + "all", + [ + "==", + "$type", + "Point" + ], + [ + "==", + "type", + "city" + ] + ], + "layout": { + "text-field": "{name_en}", + "text-font": [ + "Open Sans Bold", + "Arial Unicode MS Bold" + ], + "text-max-width": 10, + "text-size": { + "stops": [ + [ + 3, + 12 + ], + [ + 8, + 16 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 1, + "text-halo-blur": 1 + } + }, + { + "id": "country_label", + "type": "symbol", + "source": "mapbox", + "source-layer": "country_label", + "maxzoom": 12, + "filter": [ + "==", + "$type", + "Point" + ], + "layout": { + "text-field": "{name_en}", + "text-font": [ + "Open Sans Regular", + "Arial Unicode MS Regular" + ], + "text-max-width": 10, + "text-size": { + "stops": [ + [ + 3, + 14 + ], + [ + 8, + 22 + ] + ] + } + }, + "paint": { + "text-color": "#666", + "text-halo-color": "rgba(255,255,255,0.75)", + "text-halo-width": 1, + "text-halo-blur": 1 + } + }, + { + "id": "road-heatmap", + "type": "heatmap", + "source": "mapbox", + "source-layer": "road", + "paint": { + "heatmap-intensity": 1 + } + } + ] +} diff --git a/platform/macos/app/resources/heatmap.svg b/platform/macos/app/resources/heatmap.svg new file mode 100644 index 0000000000..fa2a46590a --- /dev/null +++ b/platform/macos/app/resources/heatmap.svg @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + viewBox="0 0 16 16" + id="svg4148" + version="1.1" + inkscape:version="0.92.2 5c3e80d, 2017-08-06" + sodipodi:docname="heatmap.svg" + inkscape:export-filename="/Users/mxn/Desktop/symbol.png" + inkscape:export-xdpi="90.000244" + inkscape:export-ydpi="90.000244"> + <defs + id="defs4150" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="23.25" + inkscape:cx="4.7741936" + inkscape:cy="7.6812068" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + units="px" + inkscape:window-width="1280" + inkscape:window-height="755" + inkscape:window-x="0" + inkscape:window-y="1" + inkscape:window-maximized="1" + inkscape:snap-bbox="true" /> + <metadata + id="metadata4153"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,-1036.3622)"> + <path + style="opacity:1;fill:#ffffff;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:0.99369001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 11.237265,1036.8591 a 4.2655011,4.1972104 0 0 0 -4.2658922,4.1979 4.2655011,4.1972104 0 0 0 0.025231,0.427 5.3408028,5.2552965 0 0 0 -1.1586611,-0.1299 5.3408028,5.2552965 0 0 0 -5.34109749,5.2556 5.3408028,5.2552965 0 0 0 5.34109749,5.2557 5.3408028,5.2552965 0 0 0 5.3410983,-5.2557 5.3408028,5.2552965 0 0 0 -0.186318,-1.3664 4.2655011,4.1972104 0 0 0 0.244542,0.012 4.2655011,4.1972104 0 0 0 4.26589,-4.198 4.2655011,4.1972104 0 0 0 -4.26589,-4.1979 z" + id="path818" + inkscape:connector-curvature="0" /> + <path + style="opacity:1;fill:#000000;fill-opacity:0.15686275;fill-rule:evenodd;stroke:none;stroke-width:0.99369001;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="m 11.005859,1038.875 c -4.8895767,1.3679 0.321278,7.2823 2.416016,2.7578 0.450567,-1.4666 -0.923376,-2.9624 -2.416016,-2.7578 z m -5.5078121,4.5 c -5.49629855,0.4756 -2.1892763,9.0394 2.2363281,5.9121 2.667642,-1.8963 1.5392118,-6.8607 -2.2363281,-5.9121 z" + id="path836" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index 4a16066a2a..de838cd78f 100644 --- a/platform/macos/docs/guides/For Style Authors.md +++ b/platform/macos/docs/guides/For Style Authors.md @@ -96,6 +96,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 @@ -136,6 +137,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` @@ -177,6 +179,7 @@ In style JSON | In the SDK `circle` | `MGLCircleStyleLayer` `fill` | `MGLFillStyleLayer` `fill-extrusion` | `MGLFillExtrusionStyleLayer` +`heatmap` | `MGLHeatmapStyleLayer` `hillshade` | `MGLHillshadeStyleLayer` `line` | `MGLLineStyleLayer` `raster` | `MGLRasterStyleLayer` diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml index 682ccd99ff..b4dccb620f 100644 --- a/platform/macos/jazzy.yml +++ b/platform/macos/jazzy.yml @@ -80,6 +80,7 @@ custom_categories: - MGLCircleStyleLayer - MGLFillStyleLayer - MGLFillExtrusionStyleLayer + - MGLHeatmapStyleLayer - MGLHillshadeStyleLayer - MGLLineStyleLayer - MGLSymbolStyleLayer diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index e42885f879..5af3d17b50 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 07D9474D1F67441B00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */; }; 07F8E2F71F674C8800F794BB /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 07F8E2F81F674C9000F794BB /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */; }; + 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */; }; + 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */; }; 1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */; }; 1F7454A31ECFB00300021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */; }; 1F7454A41ECFB00300021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A11ECFB00300021D39 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -85,6 +87,9 @@ 55D120A31F7906E6004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; }; 55D120A51F790A0C004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; }; 55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */; }; + 89462399200D199100DA8EF2 /* heatmap.json in Resources */ = {isa = PBXBuildFile; fileRef = 89462398200D199100DA8EF2 /* heatmap.json */; }; + 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */; }; 92092EF01F5EB10E00AF5130 /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92092EF11F5EB10E00AF5130 /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */; }; 920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */; }; @@ -298,6 +303,8 @@ 07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = "<group>"; }; 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = "<group>"; }; 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = "<group>"; }; + 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayerTests.mm; sourceTree = "<group>"; }; + 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapColorTests.mm; sourceTree = "<group>"; }; 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = "<group>"; }; 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = "<group>"; }; 1F7454A11ECFB00300021D39 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = "<group>"; }; @@ -369,6 +376,9 @@ 55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflineStorageTests.mm; path = ../../darwin/test/MGLOfflineStorageTests.mm; sourceTree = "<group>"; }; 55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = "<group>"; }; + 89462398200D199100DA8EF2 /* heatmap.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = heatmap.json; sourceTree = "<group>"; }; + 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = "<group>"; }; + 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = "<group>"; }; 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = "<group>"; }; 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = "<group>"; }; 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLSourceQueryTests.m; sourceTree = "<group>"; }; @@ -676,6 +686,8 @@ 35602BF91D3EA99F0050646F /* MGLFillStyleLayer.mm */, 35602BFD1D3EA9B40050646F /* MGLForegroundStyleLayer.h */, 35602BFE1D3EA9B40050646F /* MGLForegroundStyleLayer.mm */, + 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */, + 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */, DAF25714201901C200367EF5 /* MGLHillshadeStyleLayer.h */, DAF25713201901C100367EF5 /* MGLHillshadeStyleLayer.mm */, DA8F25891D51CA540010E6B5 /* MGLLineStyleLayer.h */, @@ -829,6 +841,7 @@ DA839EA61CC2E3400062CAFB /* Info.plist */, 96E027331E57C9A7004B8E66 /* Localizable.strings */, DA839E981CC2E3400062CAFB /* Supporting Files */, + 89462398200D199100DA8EF2 /* heatmap.json */, ); name = "Demo App"; path = app; @@ -879,6 +892,8 @@ DA8F257C1D51C5F40010E6B5 /* Layers */ = { isa = PBXGroup; children = ( + 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */, + 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */, 40E1601A1DF216E6005EA6D9 /* MGLStyleLayerTests.h */, 40E1601B1DF216E6005EA6D9 /* MGLStyleLayerTests.m */, DA2207BA1DC076930002F84D /* test-Bridging-Header.h */, @@ -1236,6 +1251,7 @@ 359819591E02F611008FC139 /* NSCoder+MGLAdditions.h in Headers */, DAE6C38E1CC31E2A00DB3429 /* MGLOfflineStorage_Private.h in Headers */, DA87A9A01DC9DC6200810D09 /* MGLValueEvaluator.h in Headers */, + 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */, DAE6C3601CC31E0400DB3429 /* MGLOfflineRegion.h in Headers */, DAE6C3681CC31E0400DB3429 /* MGLTilePyramidOfflineRegion.h in Headers */, DA35A2CF1CCAAED300E826B2 /* NSValue+MGLAdditions.h in Headers */, @@ -1427,6 +1443,7 @@ DA839EA01CC2E3400062CAFB /* MapDocument.xib in Resources */, 353BAEF81D6463B8009A8DA9 /* amsterdam.geojson in Resources */, 96E027311E57C9A7004B8E66 /* Localizable.strings in Resources */, + 89462399200D199100DA8EF2 /* heatmap.json in Resources */, DA839EA51CC2E3400062CAFB /* MainMenu.xib in Resources */, DA5589771D320C41006B7F64 /* wms.json in Resources */, DAE6C2E21CC304F900DB3429 /* Credits.rtf in Resources */, @@ -1508,6 +1525,7 @@ 07D9474B1F6743F000E37934 /* MGLAbstractShapeSource.mm in Sources */, DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */, DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */, + 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */, 3527428E1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.mm in Sources */, 35602C011D3EA9B40050646F /* MGLForegroundStyleLayer.mm in Sources */, 408AA86A1DAEEE5D00022900 /* NSDictionary+MGLAdditions.mm in Sources */, @@ -1570,11 +1588,13 @@ DAE6C3D21CC34C9900DB3429 /* MGLGeometryTests.mm in Sources */, DA87A9A41DCACC5000810D09 /* MGLSymbolStyleLayerTests.mm in Sources */, 40E1601D1DF217D6005EA6D9 /* MGLStyleLayerTests.m in Sources */, + 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */, 1F95931B1E6DE2B600D5B294 /* MGLNSDateAdditionsTests.mm in Sources */, DAF25721201902C100367EF5 /* MGLHillshadeStyleLayerTests.mm in Sources */, DA87A9A61DCACC5000810D09 /* MGLCircleStyleLayerTests.mm in Sources */, DA87A99E1DC9DC2100810D09 /* MGLPredicateTests.mm in Sources */, DD58A4C91D822C6700E1F038 /* MGLExpressionTests.mm in Sources */, + 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */, 4031ACFC1E9EB3C100A3EA26 /* MGLMapViewDelegateIntegrationTests.swift in Sources */, 4031AD031E9FD6AA00A3EA26 /* MGLSDKTestHelpers.swift in Sources */, DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.mm in Sources */, diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 7ea9a9dd1d..0e4b546cf7 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -44,6 +44,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLRasterStyleLayer.h" #import "MGLCircleStyleLayer.h" #import "MGLBackgroundStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLOpenGLStyleLayer.h" #import "MGLSource.h" diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 3e66aaa789..ac14df0228 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -15,6 +15,7 @@ #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index ab187a86b3..42751da30f 100644 --- a/platform/node/test/ignores.json +++ b/platform/node/test/ignores.json @@ -9,6 +9,7 @@ "render-tests/background-color/transition": "https://github.com/mapbox/mapbox-gl-native/issues/10619", "render-tests/debug/collision": "https://github.com/mapbox/mapbox-gl-native/issues/3841", "render-tests/debug/collision-lines": "https://github.com/mapbox/mapbox-gl-native/issues/10412", + "render-tests/debug/collision-lines-overscaled": "https://github.com/mapbox/mapbox-gl-native/issues/10412", "render-tests/debug/collision-lines-pitched": "https://github.com/mapbox/mapbox-gl-native/issues/10412", "render-tests/debug/collision-pitched-wrapped": "https://github.com/mapbox/mapbox-gl-native/issues/10412", "render-tests/debug/tile": "https://github.com/mapbox/mapbox-gl-native/issues/3841", @@ -22,24 +23,6 @@ "render-tests/fill-extrusion-pattern/opacity": "https://github.com/mapbox/mapbox-gl-js/issues/3327", "render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary", "render-tests/geojson/inline-polygon-symbol": "behavior needs reconciliation with gl-js", - "render-tests/heatmap-color/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-color/expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-color/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-intensity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-intensity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-intensity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-opacity/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-opacity/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-opacity/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/antimeridian": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/data-expression": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-radius/pitch30": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-weight/default": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-weight/identity-property-function": "https://github.com/mapbox/mapbox-gl-native/issues/10146", - "render-tests/heatmap-weight/literal": "https://github.com/mapbox/mapbox-gl-native/issues/10146", "render-tests/mixed-zoom/z10-z11": "https://github.com/mapbox/mapbox-gl-native/issues/10397", "render-tests/raster-masking/overlapping-zoom": "https://github.com/mapbox/mapbox-gl-native/issues/10195", "render-tests/regressions/mapbox-gl-js#2305": "https://github.com/mapbox/mapbox-gl-native/issues/6927", @@ -84,6 +67,10 @@ "render-tests/combinations/line-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146", "render-tests/combinations/raster-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146", "render-tests/combinations/symbol-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146", + "render-tests/combinations/background-opaque--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642", + "render-tests/combinations/background-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642", + "render-tests/combinations/circle-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642", + "render-tests/combinations/fill-extrusion-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642", "render-tests/combinations/background-opaque--fill-extrusion-translucent": "needs investigation", "render-tests/combinations/background-translucent--fill-extrusion-translucent": "needs investigation", "render-tests/combinations/circle-translucent--fill-extrusion-translucent": "needs investigation", diff --git a/platform/qt/app/mapwindow.cpp b/platform/qt/app/mapwindow.cpp index fc346cdcde..f6d5473192 100644 --- a/platform/qt/app/mapwindow.cpp +++ b/platform/qt/app/mapwindow.cpp @@ -442,10 +442,9 @@ void MapWindow::initializeGL() void MapWindow::paintGL() { m_frameDraws++; - m_map->resize(size(), size() * pixelRatio()); + m_map->resize(size()); #if QT_VERSION >= 0x050400 - // When we're using QOpenGLWidget, we need to tell Mapbox GL about the framebuffer we're using. - m_map->setFramebufferObject(defaultFramebufferObject()); + m_map->setFramebufferObject(defaultFramebufferObject(), size() * pixelRatio()); #endif m_map->render(); } diff --git a/platform/qt/include/qmapboxgl.hpp b/platform/qt/include/qmapboxgl.hpp index 8b319b0453..bc18eaba59 100644 --- a/platform/qt/include/qmapboxgl.hpp +++ b/platform/qt/include/qmapboxgl.hpp @@ -26,6 +26,11 @@ public: SharedGLContext }; + enum MapMode { + Continuous = 0, + Static + }; + enum ConstrainMode { NoConstrain = 0, ConstrainHeightOnly, @@ -40,6 +45,9 @@ public: GLContextMode contextMode() const; void setContextMode(GLContextMode); + MapMode mapMode() const; + void setMapMode(MapMode); + ConstrainMode constrainMode() const; void setConstrainMode(ConstrainMode); @@ -66,6 +74,7 @@ public: private: GLContextMode m_contextMode; + MapMode m_mapMode; ConstrainMode m_constrainMode; ViewportMode m_viewportMode; @@ -191,8 +200,7 @@ public: void scaleBy(double scale, const QPointF ¢er = QPointF()); void rotateBy(const QPointF &first, const QPointF &second); - void resize(const QSize &size, const QSize &framebufferSize); - void setFramebufferObject(quint32 fbo); + void resize(const QSize &size); double metersPerPixelAtLatitude(double latitude, double zoom) const; QMapbox::ProjectedMeters projectedMetersForCoordinate(const QMapbox::Coordinate &) const; @@ -226,15 +234,27 @@ public: void setFilter(const QString &layer, const QVariant &filter); + // When rendering on a different thread, + // should be called on the render thread. + void createRenderer(); + void destroyRenderer(); + void setFramebufferObject(quint32 fbo, const QSize &size); + public slots: void render(); void connectionEstablished(); + // Commit changes, load all the resources + // and renders the map when completed. + void startStaticRender(); + signals: void needsRendering(); void mapChanged(QMapboxGL::MapChange); void copyrightsChanged(const QString ©rightsHtml); + void staticRenderFinished(const QString &error); + private: Q_DISABLE_COPY(QMapboxGL) diff --git a/platform/qt/qt.cmake b/platform/qt/qt.cmake index aaae199650..d1d597bda6 100644 --- a/platform/qt/qt.cmake +++ b/platform/qt/qt.cmake @@ -61,8 +61,12 @@ add_library(qmapboxgl SHARED platform/qt/src/qmapbox.cpp platform/qt/src/qmapboxgl.cpp platform/qt/src/qmapboxgl_p.hpp - platform/qt/src/qmapboxgl_renderer_frontend_p.hpp - platform/qt/src/qmapboxgl_renderer_frontend_p.cpp + platform/qt/src/qmapboxgl_map_observer.cpp + platform/qt/src/qmapboxgl_map_observer.hpp + platform/qt/src/qmapboxgl_map_renderer.cpp + platform/qt/src/qmapboxgl_map_renderer.hpp + platform/qt/src/qmapboxgl_renderer_backend.hpp + platform/qt/src/qmapboxgl_renderer_backend.cpp platform/default/mbgl/util/default_styles.hpp ) diff --git a/platform/qt/src/qmapboxgl.cpp b/platform/qt/src/qmapboxgl.cpp index 2675d87862..414b65255c 100644 --- a/platform/qt/src/qmapboxgl.cpp +++ b/platform/qt/src/qmapboxgl.cpp @@ -1,6 +1,8 @@ #include "qmapboxgl.hpp" #include "qmapboxgl_p.hpp" -#include "qmapboxgl_renderer_frontend_p.hpp" + +#include "qmapboxgl_map_observer.hpp" +#include "qmapboxgl_renderer_observer.hpp" #include "qt_conversion.hpp" #include "qt_geojson.hpp" @@ -43,11 +45,8 @@ #if QT_VERSION >= 0x050000 #include <QGuiApplication> -#include <QWindow> -#include <QOpenGLContext> #else #include <QCoreApplication> -#include <QGLContext> #endif #include <QDebug> @@ -65,6 +64,10 @@ using namespace QMapbox; static_assert(mbgl::underlying_type(QMapboxGLSettings::UniqueGLContext) == mbgl::underlying_type(mbgl::GLContextMode::Unique), "error"); static_assert(mbgl::underlying_type(QMapboxGLSettings::SharedGLContext) == mbgl::underlying_type(mbgl::GLContextMode::Shared), "error"); +// mbgl::MapMode +static_assert(mbgl::underlying_type(QMapboxGLSettings::Continuous) == mbgl::underlying_type(mbgl::MapMode::Continuous), "error"); +static_assert(mbgl::underlying_type(QMapboxGLSettings::Static) == mbgl::underlying_type(mbgl::MapMode::Static), "error"); + // mbgl::ConstrainMode static_assert(mbgl::underlying_type(QMapboxGLSettings::NoConstrain) == mbgl::underlying_type(mbgl::ConstrainMode::None), "error"); static_assert(mbgl::underlying_type(QMapboxGLSettings::ConstrainHeightOnly) == mbgl::underlying_type(mbgl::ConstrainMode::HeightOnly), "error"); @@ -165,6 +168,27 @@ std::unique_ptr<mbgl::style::Image> toStyleImage(const QString &id, const QImage */ /*! + \enum QMapboxGLSettings::MapMode + + This enum sets the map rendering mode + + \value Continuous The map will render as data arrives from the network and + react immediately to state changes. + + This is the default mode and the preferred when the map is intended to be + interactive. + + \value Static The map will no longer react to state changes and will only + be rendered when QMapboxGL::startStaticRender is called. After all the + resources are loaded, the QMapboxGL::staticRenderFinished signal is emitted. + + This mode is useful for taking a snapshot of the finished rendering result + of the map into a QImage. + + \sa mapMode() +*/ + +/*! \enum QMapboxGLSettings::ConstrainMode This enum determines if the map wraps. @@ -200,6 +224,7 @@ std::unique_ptr<mbgl::style::Image> toStyleImage(const QString &id, const QImage */ QMapboxGLSettings::QMapboxGLSettings() : m_contextMode(QMapboxGLSettings::SharedGLContext) + , m_mapMode(QMapboxGLSettings::Continuous) , m_constrainMode(QMapboxGLSettings::ConstrainHeightOnly) , m_viewportMode(QMapboxGLSettings::DefaultViewport) , m_cacheMaximumSize(mbgl::util::DEFAULT_MAX_CACHE_SIZE) @@ -230,6 +255,31 @@ void QMapboxGLSettings::setContextMode(GLContextMode mode) } /*! + Returns the map mode. Static mode will emit a signal for + rendering a map only when the map is fully loaded. + Animations like style transitions and labels fading won't + be seen. + + The Continuous mode will emit the signal for every new + change on the map and it is usually what you expect for + a interactive map. + + By default, it is set to QMapboxGLSettings::Continuous. +*/ +QMapboxGLSettings::MapMode QMapboxGLSettings::mapMode() const +{ + return m_mapMode; +} + +/*! + Sets the map \a mode. +*/ +void QMapboxGLSettings::setMapMode(MapMode mode) +{ + m_mapMode = mode; +} + +/*! Returns the constrain mode. This is used to limit the map to wrap around the globe horizontally. @@ -1048,32 +1098,17 @@ void QMapboxGL::rotateBy(const QPointF &first, const QPointF &second) } /*! - Resize the map to \a size and scale to fit at \a framebufferSize. For - high DPI screens, the size will be smaller than the \a framebufferSize. - - This fallowing example will double the pixel density of the map for - a given \c size: - - \code - map->resize(size / 2, size); - \endcode + Resize the map to \a size_ and scale to fit at the framebuffer. For + high DPI screens, the size will be smaller than the framebuffer. */ -void QMapboxGL::resize(const QSize& size, const QSize& framebufferSize) +void QMapboxGL::resize(const QSize& size_) { - if (d_ptr->size == size && d_ptr->fbSize == framebufferSize) return; - - d_ptr->size = size; - d_ptr->fbSize = framebufferSize; + auto size = sanitizedSize(size_); - d_ptr->mapObj->setSize(sanitizedSize(size)); -} + if (d_ptr->mapObj->getSize() == size) + return; -/*! - If Mapbox GL needs to rebind the default \a fbo, it will use the - ID supplied here. -*/ -void QMapboxGL::setFramebufferObject(quint32 fbo) { - d_ptr->fbObject = fbo; + d_ptr->mapObj->setSize(size); } /*! @@ -1468,6 +1503,51 @@ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter) } /*! + Creates the infrastructure needed for rendering the map. It + should be called before any call to render(). + + Must be called on the render thread. +*/ +void QMapboxGL::createRenderer() +{ + d_ptr->createRenderer(); +} + +/*! + Destroys the infrastructure needed for rendering the map, + releasing resources. + + Must be called on the render thread. +*/ +void QMapboxGL::destroyRenderer() +{ + d_ptr->destroyRenderer(); +} + +/*! + Start a static rendering of the current state of the map. This + should only be called when the map is initialized in static mode. + + \sa QMapboxGLSettings::MapMode +*/ +void QMapboxGL::startStaticRender() +{ + d_ptr->mapObj->renderStill([this](std::exception_ptr err) { + QString what; + + try { + if (err) { + std::rethrow_exception(err); + } + } catch(const std::exception& e) { + what = e.what(); + } + + emit staticRenderFinished(what); + }); +} + +/*! Renders the map using OpenGL draw calls. It will make sure to bind the framebuffer object before drawing; otherwise a valid OpenGL context is expected with an appropriate OpenGL viewport state set for the size of @@ -1475,28 +1555,33 @@ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter) This function should be called only after the signal needsRendering() is emitted at least once. + + Must be called on the render thread. */ void QMapboxGL::render() { -#if defined(__APPLE__) && QT_VERSION < 0x050000 - // FIXME Qt 4.x provides an incomplete FBO at start. - // See https://bugreports.qt.io/browse/QTBUG-36802 for details. - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - return; - } -#endif - - d_ptr->dirty = false; d_ptr->render(); } /*! + If Mapbox GL needs to rebind the default \a fbo, it will use the + ID supplied here. \a size is the size of the framebuffer, which + on high DPI screens is usually bigger than the map size. + + Must be called on the render thread. +*/ +void QMapboxGL::setFramebufferObject(quint32 fbo, const QSize& size) +{ + d_ptr->setFramebufferObject(fbo, size); +} + +/*! Informs the map that the network connection has been established, causing all network requests that previously timed out to be retried immediately. */ void QMapboxGL::connectionEstablished() { - d_ptr->connectionEstablished(); + mbgl::NetworkStatus::Reachable(); } /*! @@ -1510,6 +1595,16 @@ void QMapboxGL::connectionEstablished() */ /*! + \fn void QMapboxGL::staticRenderFinished(const QString &error) + + This signal is emitted when a static map is fully drawn. Usually the next + step is to extract the map from a framebuffer into a container like a + QImage. \a error is set to a message when an error occurs. + + \sa startStaticRender() +*/ + +/*! \fn void QMapboxGL::mapChanged(QMapboxGL::MapChange change) This signal is emitted when the state of the map has changed. This signal @@ -1526,186 +1621,140 @@ void QMapboxGL::connectionEstablished() \a copyrightsHtml is a string with a HTML snippet. */ -class QMapboxGLRendererFrontend; - -QMapboxGLPrivate::QMapboxGLPrivate(QMapboxGL *q, const QMapboxGLSettings &settings, const QSize &size_, qreal pixelRatio) +QMapboxGLPrivate::QMapboxGLPrivate(QMapboxGL *q, const QMapboxGLSettings &settings, const QSize &size, qreal pixelRatio_) : QObject(q) - , size(size_) - , q_ptr(q) - , fileSourceObj(sharedDefaultFileSource( + , m_fileSourceObj(sharedDefaultFileSource( settings.cacheDatabasePath().toStdString(), settings.assetPath().toStdString(), settings.cacheDatabaseMaximumSize())) - , threadPool(mbgl::sharedThreadPool()) + , m_threadPool(mbgl::sharedThreadPool()) + , m_mode(settings.contextMode()) + , m_pixelRatio(pixelRatio_) { - // Setup resource transform if needed + // Setup the FileSource + m_fileSourceObj->setAccessToken(settings.accessToken().toStdString()); + m_fileSourceObj->setAPIBaseURL(settings.apiBaseUrl().toStdString()); + if (settings.resourceTransform()) { - m_resourceTransform = - std::make_unique< mbgl::Actor<mbgl::ResourceTransform> >( *mbgl::Scheduler::GetCurrent(), - [callback = settings.resourceTransform()] - (mbgl::Resource::Kind , const std::string&& url_) -> std::string { - return callback(std::move(url_)); - } - ); - fileSourceObj->setResourceTransform(m_resourceTransform->self()); + m_resourceTransform = std::make_unique<mbgl::Actor<mbgl::ResourceTransform>>(*mbgl::Scheduler::GetCurrent(), + [callback = settings.resourceTransform()] (mbgl::Resource::Kind, const std::string &&url_) -> std::string { + return callback(std::move(url_)); + }); + m_fileSourceObj->setResourceTransform(m_resourceTransform->self()); } - // Setup and connect the renderer frontend - frontend = std::make_unique<QMapboxGLRendererFrontend>( - std::make_unique<mbgl::Renderer>(*this, pixelRatio, *fileSourceObj, *threadPool, - static_cast<mbgl::GLContextMode>(settings.contextMode())), - *this); - connect(frontend.get(), SIGNAL(updated()), this, SLOT(invalidate())); + // Setup MapObserver + m_mapObserver = std::make_unique<QMapboxGLMapObserver>(this); + + qRegisterMetaType<QMapboxGL::MapChange>("QMapboxGL::MapChange"); + + connect(m_mapObserver.get(), SIGNAL(mapChanged(QMapboxGL::MapChange)), q, SIGNAL(mapChanged(QMapboxGL::MapChange))); + connect(m_mapObserver.get(), SIGNAL(copyrightsChanged(QString)), q, SIGNAL(copyrightsChanged(QString))); + // Setup the Map object mapObj = std::make_unique<mbgl::Map>( - *frontend, - *this, sanitizedSize(size), - pixelRatio, *fileSourceObj, *threadPool, - mbgl::MapMode::Continuous, + *this, // RendererFrontend + *m_mapObserver, + sanitizedSize(size), + m_pixelRatio, *m_fileSourceObj, *m_threadPool, + static_cast<mbgl::MapMode>(settings.mapMode()), static_cast<mbgl::ConstrainMode>(settings.constrainMode()), static_cast<mbgl::ViewportMode>(settings.viewportMode())); - qRegisterMetaType<QMapboxGL::MapChange>("QMapboxGL::MapChange"); - - fileSourceObj->setAccessToken(settings.accessToken().toStdString()); - fileSourceObj->setAPIBaseURL(settings.apiBaseUrl().toStdString()); - - connect(this, SIGNAL(needsRendering()), q_ptr, SIGNAL(needsRendering()), Qt::QueuedConnection); - connect(this, SIGNAL(mapChanged(QMapboxGL::MapChange)), q_ptr, SIGNAL(mapChanged(QMapboxGL::MapChange)), Qt::QueuedConnection); - connect(this, SIGNAL(copyrightsChanged(QString)), q_ptr, SIGNAL(copyrightsChanged(QString)), Qt::QueuedConnection); + // Needs to be Queued to give time to discard redundant draw calls via the `renderQueued` flag. + connect(this, SIGNAL(needsRendering()), q, SIGNAL(needsRendering()), Qt::QueuedConnection); } QMapboxGLPrivate::~QMapboxGLPrivate() { } -mbgl::Size QMapboxGLPrivate::getFramebufferSize() const { - return sanitizedSize(fbSize); -} +void QMapboxGLPrivate::update(std::shared_ptr<mbgl::UpdateParameters> parameters) +{ + std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex); -void QMapboxGLPrivate::updateAssumedState() { - assumeFramebufferBinding(fbObject); -#if QT_VERSION >= 0x050600 - assumeViewport(0, 0, getFramebufferSize()); -#endif -} + if (!m_mapRenderer) { + return; + } -void QMapboxGLPrivate::bind() { - setFramebufferBinding(fbObject); - setViewport(0, 0, getFramebufferSize()); -} + m_mapRenderer->updateParameters(std::move(parameters)); -void QMapboxGLPrivate::invalidate() -{ - if (!dirty) { - emit needsRendering(); - dirty = true; - } + requestRendering(); } -void QMapboxGLPrivate::render() +void QMapboxGLPrivate::setObserver(mbgl::RendererObserver &observer) { - frontend->render(); -} + m_rendererObserver = std::make_shared<QMapboxGLRendererObserver>( + *mbgl::util::RunLoop::Get(), observer); -void QMapboxGLPrivate::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) -{ - if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) { - emit mapChanged(QMapboxGL::MapChangeRegionWillChange); - } else { - emit mapChanged(QMapboxGL::MapChangeRegionWillChangeAnimated); + std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex); + + if (m_mapRenderer) { + m_mapRenderer->setObserver(m_rendererObserver); } } -void QMapboxGLPrivate::onCameraIsChanging() +void QMapboxGLPrivate::createRenderer() { - emit mapChanged(QMapboxGL::MapChangeRegionIsChanging); -} + std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex); -void QMapboxGLPrivate::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) -{ - if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) { - emit mapChanged(QMapboxGL::MapChangeRegionDidChange); - } else { - emit mapChanged(QMapboxGL::MapChangeRegionDidChangeAnimated); + if (m_mapRenderer) { + return; } -} -void QMapboxGLPrivate::onWillStartLoadingMap() -{ - emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap); -} + m_mapRenderer = std::make_unique<QMapboxGLMapRenderer>( + m_pixelRatio, + *m_fileSourceObj, + *m_threadPool, + m_mode + ); -void QMapboxGLPrivate::onDidFinishLoadingMap() -{ - emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingMap); -} + connect(m_mapRenderer.get(), SIGNAL(needsRendering()), this, SLOT(requestRendering())); -void QMapboxGLPrivate::onDidFailLoadingMap(std::exception_ptr) -{ - emit mapChanged(QMapboxGL::MapChangeDidFailLoadingMap); + m_mapRenderer->setObserver(m_rendererObserver); } -void QMapboxGLPrivate::onWillStartRenderingFrame() +void QMapboxGLPrivate::destroyRenderer() { - emit mapChanged(QMapboxGL::MapChangeWillStartRenderingFrame); -} + std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex); -void QMapboxGLPrivate::onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode) -{ - if (mode == mbgl::MapObserver::RenderMode::Partial) { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrame); - } else { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrameFullyRendered); - } + m_mapRenderer.reset(); } -void QMapboxGLPrivate::onWillStartRenderingMap() +void QMapboxGLPrivate::render() { - emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap); -} + std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex); -void QMapboxGLPrivate::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) -{ - if (mode == mbgl::MapObserver::RenderMode::Partial) { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMap); - } else { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMapFullyRendered); + if (!m_mapRenderer) { + createRenderer(); } -} -void QMapboxGLPrivate::onDidFinishLoadingStyle() -{ - emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingStyle); +#if defined(__APPLE__) && QT_VERSION < 0x050000 + // FIXME Qt 4.x provides an incomplete FBO at start. + // See https://bugreports.qt.io/browse/QTBUG-36802 for details. + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return; + } +#endif + + m_renderQueued.clear(); + m_mapRenderer->render(); } -void QMapboxGLPrivate::onSourceChanged(mbgl::style::Source&) +void QMapboxGLPrivate::setFramebufferObject(quint32 fbo, const QSize& size) { - std::string attribution; - for (const auto& source : mapObj->getStyle().getSources()) { - // Avoid duplicates by using the most complete attribution HTML snippet. - if (source->getAttribution() && (attribution.size() < source->getAttribution()->size())) - attribution = *source->getAttribution(); + std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex); + + if (!m_mapRenderer) { + createRenderer(); } - emit copyrightsChanged(QString::fromStdString(attribution)); - emit mapChanged(QMapboxGL::MapChangeSourceDidChange); -} -/*! - Initializes an OpenGL extension function such as Vertex Array Objects (VAOs), - required by Mapbox GL Native engine. -*/ -mbgl::gl::ProcAddress QMapboxGLPrivate::getExtensionFunctionPointer(const char* name) { -#if QT_VERSION >= 0x050000 - QOpenGLContext* thisContext = QOpenGLContext::currentContext(); - return thisContext->getProcAddress(name); -#else - const QGLContext* thisContext = QGLContext::currentContext(); - return reinterpret_cast<mbgl::gl::ProcAddress>(thisContext->getProcAddress(name)); -#endif + m_mapRenderer->updateFramebuffer(fbo, sanitizedSize(size)); } -void QMapboxGLPrivate::connectionEstablished() +void QMapboxGLPrivate::requestRendering() { - mbgl::NetworkStatus::Reachable(); + if (!m_renderQueued.test_and_set()) { + emit needsRendering(); + } } diff --git a/platform/qt/src/qmapboxgl_map_observer.cpp b/platform/qt/src/qmapboxgl_map_observer.cpp new file mode 100644 index 0000000000..e180ed8fda --- /dev/null +++ b/platform/qt/src/qmapboxgl_map_observer.cpp @@ -0,0 +1,95 @@ +#include "qmapboxgl_map_observer.hpp" + +#include "qmapboxgl_p.hpp" + +QMapboxGLMapObserver::QMapboxGLMapObserver(QMapboxGLPrivate *d) + : d_ptr(d) +{ +} + +QMapboxGLMapObserver::~QMapboxGLMapObserver() +{ +} + +void QMapboxGLMapObserver::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) +{ + if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) { + emit mapChanged(QMapboxGL::MapChangeRegionWillChange); + } else { + emit mapChanged(QMapboxGL::MapChangeRegionWillChangeAnimated); + } +} + +void QMapboxGLMapObserver::onCameraIsChanging() +{ + emit mapChanged(QMapboxGL::MapChangeRegionIsChanging); +} + +void QMapboxGLMapObserver::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) +{ + if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) { + emit mapChanged(QMapboxGL::MapChangeRegionDidChange); + } else { + emit mapChanged(QMapboxGL::MapChangeRegionDidChangeAnimated); + } +} + +void QMapboxGLMapObserver::onWillStartLoadingMap() +{ + emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap); +} + +void QMapboxGLMapObserver::onDidFinishLoadingMap() +{ + emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingMap); +} + +void QMapboxGLMapObserver::onDidFailLoadingMap(std::exception_ptr) +{ + emit mapChanged(QMapboxGL::MapChangeDidFailLoadingMap); +} + +void QMapboxGLMapObserver::onWillStartRenderingFrame() +{ + emit mapChanged(QMapboxGL::MapChangeWillStartRenderingFrame); +} + +void QMapboxGLMapObserver::onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode) +{ + if (mode == mbgl::MapObserver::RenderMode::Partial) { + emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrame); + } else { + emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrameFullyRendered); + } +} + +void QMapboxGLMapObserver::onWillStartRenderingMap() +{ + emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap); +} + +void QMapboxGLMapObserver::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) +{ + if (mode == mbgl::MapObserver::RenderMode::Partial) { + emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMap); + } else { + emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMapFullyRendered); + } +} + +void QMapboxGLMapObserver::onDidFinishLoadingStyle() +{ + emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingStyle); +} + +void QMapboxGLMapObserver::onSourceChanged(mbgl::style::Source&) +{ + std::string attribution; + for (const auto& source : d_ptr->mapObj->getStyle().getSources()) { + // Avoid duplicates by using the most complete attribution HTML snippet. + if (source->getAttribution() && (attribution.size() < source->getAttribution()->size())) + attribution = *source->getAttribution(); + } + emit copyrightsChanged(QString::fromStdString(attribution)); + emit mapChanged(QMapboxGL::MapChangeSourceDidChange); +} diff --git a/platform/qt/src/qmapboxgl_map_observer.hpp b/platform/qt/src/qmapboxgl_map_observer.hpp new file mode 100644 index 0000000000..c9d0581a90 --- /dev/null +++ b/platform/qt/src/qmapboxgl_map_observer.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "qmapboxgl.hpp" + +#include <mbgl/map/map_observer.hpp> +#include <mbgl/style/style.hpp> + +#include <QObject> + +#include <exception> +#include <memory> + +class QMapboxGLPrivate; + +class QMapboxGLMapObserver : public QObject, public mbgl::MapObserver +{ + Q_OBJECT + +public: + explicit QMapboxGLMapObserver(QMapboxGLPrivate *); + virtual ~QMapboxGLMapObserver(); + + // mbgl::MapObserver implementation. + void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) final; + void onCameraIsChanging() final; + void onCameraDidChange(mbgl::MapObserver::CameraChangeMode) final; + void onWillStartLoadingMap() final; + void onDidFinishLoadingMap() final; + void onDidFailLoadingMap(std::exception_ptr) final; + void onWillStartRenderingFrame() final; + void onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode) final; + void onWillStartRenderingMap() final; + void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) final; + void onDidFinishLoadingStyle() final; + void onSourceChanged(mbgl::style::Source&) final; + +signals: + void mapChanged(QMapboxGL::MapChange); + void copyrightsChanged(const QString ©rightsHtml); + +private: + Q_DISABLE_COPY(QMapboxGLMapObserver) + + QMapboxGLPrivate *d_ptr; +}; diff --git a/platform/qt/src/qmapboxgl_map_renderer.cpp b/platform/qt/src/qmapboxgl_map_renderer.cpp new file mode 100644 index 0000000000..7a9d1f6f78 --- /dev/null +++ b/platform/qt/src/qmapboxgl_map_renderer.cpp @@ -0,0 +1,86 @@ +#include "qmapboxgl_map_renderer.hpp" + +#include <QtGlobal> + +QMapboxGLMapRenderer::QMapboxGLMapRenderer(qreal pixelRatio, + mbgl::DefaultFileSource &fs, mbgl::ThreadPool &tp, QMapboxGLSettings::GLContextMode mode) + : m_renderer(std::make_unique<mbgl::Renderer>(m_backend, pixelRatio, fs, tp, static_cast<mbgl::GLContextMode>(mode))) + , m_threadWithScheduler(Scheduler::GetCurrent() != nullptr) +{ +} + +QMapboxGLMapRenderer::~QMapboxGLMapRenderer() +{ + MBGL_VERIFY_THREAD(tid); +} + +void QMapboxGLMapRenderer::schedule(std::weak_ptr<mbgl::Mailbox> mailbox) +{ + std::lock_guard<std::mutex> lock(m_taskQueueMutex); + m_taskQueue.push(mailbox); + + // Need to force the main thread to wake + // up this thread and process the events. + emit needsRendering(); +} + +void QMapboxGLMapRenderer::updateParameters(std::shared_ptr<mbgl::UpdateParameters> newParameters) +{ + std::lock_guard<std::mutex> lock(m_updateMutex); + m_updateParameters = std::move(newParameters); +} + +void QMapboxGLMapRenderer::updateFramebuffer(quint32 fbo, const mbgl::Size &size) +{ + MBGL_VERIFY_THREAD(tid); + + m_backend.updateFramebuffer(fbo, size); +} + +void QMapboxGLMapRenderer::render() +{ + MBGL_VERIFY_THREAD(tid); + + std::shared_ptr<mbgl::UpdateParameters> params; + { + // Lock on the parameters + std::lock_guard<std::mutex> lock(m_updateMutex); + + if (!m_updateParameters) { + return; + } + + // Hold on to the update parameters during render + params = m_updateParameters; + } + + // The OpenGL implementation automatically enables the OpenGL context for us. + mbgl::BackendScope scope(m_backend, mbgl::BackendScope::ScopeType::Implicit); + + // If we don't have a Scheduler on this thread, which + // is usually the case for render threads, use this + // object as scheduler. + if (!m_threadWithScheduler) { + Scheduler::SetCurrent(this); + } + + m_renderer->render(*params); + + if (!m_threadWithScheduler) { + std::queue<std::weak_ptr<mbgl::Mailbox>> taskQueue; + { + std::unique_lock<std::mutex> lock(m_taskQueueMutex); + std::swap(taskQueue, m_taskQueue); + } + + while (!taskQueue.empty()) { + mbgl::Mailbox::maybeReceive(taskQueue.front()); + taskQueue.pop(); + } + } +} + +void QMapboxGLMapRenderer::setObserver(std::shared_ptr<mbgl::RendererObserver> observer) +{ + m_renderer->setObserver(observer.get()); +} diff --git a/platform/qt/src/qmapboxgl_map_renderer.hpp b/platform/qt/src/qmapboxgl_map_renderer.hpp new file mode 100644 index 0000000000..adba11de51 --- /dev/null +++ b/platform/qt/src/qmapboxgl_map_renderer.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "qmapboxgl.hpp" +#include "qmapboxgl_renderer_backend.hpp" + +#include <mbgl/renderer/renderer.hpp> +#include <mbgl/renderer/renderer_backend.hpp> +#include <mbgl/renderer/renderer_observer.hpp> +#include <mbgl/storage/default_file_source.hpp> +#include <mbgl/util/shared_thread_pool.hpp> +#include <mbgl/util/util.hpp> + +#include <QtGlobal> + +#include <memory> +#include <mutex> +#include <queue> + +namespace mbgl { +class Renderer; +class UpdateParameters; +} // namespace mbgl + +class QMapboxGLRendererBackend; + +class QMapboxGLMapRenderer : public QObject, public mbgl::Scheduler +{ + Q_OBJECT + +public: + QMapboxGLMapRenderer(qreal pixelRatio, mbgl::DefaultFileSource &, + mbgl::ThreadPool &, QMapboxGLSettings::GLContextMode); + virtual ~QMapboxGLMapRenderer(); + + // mbgl::Scheduler implementation. + void schedule(std::weak_ptr<mbgl::Mailbox> scheduled) final; + + void render(); + void updateFramebuffer(quint32 fbo, const mbgl::Size &size); + void setObserver(std::shared_ptr<mbgl::RendererObserver>); + + // Thread-safe, called by the Frontend + void updateParameters(std::shared_ptr<mbgl::UpdateParameters>); + +signals: + void needsRendering(); + +private: + MBGL_STORE_THREAD(tid) + + Q_DISABLE_COPY(QMapboxGLMapRenderer) + + std::mutex m_updateMutex; + std::shared_ptr<mbgl::UpdateParameters> m_updateParameters; + + QMapboxGLRendererBackend m_backend; + std::unique_ptr<mbgl::Renderer> m_renderer; + + std::mutex m_taskQueueMutex; + std::queue<std::weak_ptr<mbgl::Mailbox>> m_taskQueue; + + bool m_threadWithScheduler; +}; diff --git a/platform/qt/src/qmapboxgl_p.hpp b/platform/qt/src/qmapboxgl_p.hpp index f947c09f48..51c7cb8fc4 100644 --- a/platform/qt/src/qmapboxgl_p.hpp +++ b/platform/qt/src/qmapboxgl_p.hpp @@ -1,22 +1,24 @@ #pragma once #include "qmapboxgl.hpp" -#include "qmapboxgl_renderer_frontend_p.hpp" +#include "qmapboxgl_map_observer.hpp" +#include "qmapboxgl_map_renderer.hpp" #include <mbgl/actor/actor.hpp> #include <mbgl/map/map.hpp> -#include <mbgl/renderer/renderer_backend.hpp> -#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/renderer/renderer_frontend.hpp> #include <mbgl/storage/default_file_source.hpp> -#include <mbgl/util/geo.hpp> #include <mbgl/storage/resource_transform.hpp> +#include <mbgl/util/default_thread_pool.hpp> +#include <mbgl/util/geo.hpp> #include <QObject> #include <QSize> +#include <atomic> #include <memory> -class QMapboxGLPrivate : public QObject, public mbgl::RendererBackend, public mbgl::MapObserver +class QMapboxGLPrivate : public QObject, public mbgl::RendererFrontend { Q_OBJECT @@ -24,55 +26,40 @@ public: explicit QMapboxGLPrivate(QMapboxGL *, const QMapboxGLSettings &, const QSize &size, qreal pixelRatio); virtual ~QMapboxGLPrivate(); + // mbgl::RendererFrontend implementation. + void reset() final {} + void setObserver(mbgl::RendererObserver &) final; + void update(std::shared_ptr<mbgl::UpdateParameters>) final; - // mbgl::RendererBackend implementation. - void bind() final; - mbgl::Size getFramebufferSize() const final; - void updateAssumedState() final; - void activate() final {} - void deactivate() final {} - - // mbgl::MapObserver implementation. - void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) final; - void onCameraIsChanging() final; - void onCameraDidChange(mbgl::MapObserver::CameraChangeMode) final; - void onWillStartLoadingMap() final; - void onDidFinishLoadingMap() final; - void onDidFailLoadingMap(std::exception_ptr) final; - void onWillStartRenderingFrame() final; - void onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode) final; - void onWillStartRenderingMap() final; - void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode) final; - void onDidFinishLoadingStyle() final; - void onSourceChanged(mbgl::style::Source&) final; + // These need to be called on the same thread. + void createRenderer(); + void destroyRenderer(); + void render(); + void setFramebufferObject(quint32 fbo, const QSize& size); mbgl::EdgeInsets margins; - QSize size { 0, 0 }; - QSize fbSize { 0, 0 }; - quint32 fbObject = 0; - - QMapboxGL *q_ptr { nullptr }; - - std::shared_ptr<mbgl::DefaultFileSource> fileSourceObj; - std::shared_ptr<mbgl::ThreadPool> threadPool; - std::unique_ptr<QMapboxGLRendererFrontend> frontend; std::unique_ptr<mbgl::Map> mapObj; - bool dirty { false }; - -private: - mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) override; - public slots: - void connectionEstablished(); - void invalidate(); - void render(); + void requestRendering(); signals: void needsRendering(); - void mapChanged(QMapboxGL::MapChange); - void copyrightsChanged(const QString ©rightsHtml); private: - std::unique_ptr< mbgl::Actor<mbgl::ResourceTransform> > m_resourceTransform; + Q_DISABLE_COPY(QMapboxGLPrivate) + + std::recursive_mutex m_mapRendererMutex; + std::shared_ptr<mbgl::RendererObserver> m_rendererObserver; + + std::unique_ptr<QMapboxGLMapObserver> m_mapObserver; + std::shared_ptr<mbgl::DefaultFileSource> m_fileSourceObj; + std::shared_ptr<mbgl::ThreadPool> m_threadPool; + std::unique_ptr<QMapboxGLMapRenderer> m_mapRenderer; + std::unique_ptr<mbgl::Actor<mbgl::ResourceTransform>> m_resourceTransform; + + QMapboxGLSettings::GLContextMode m_mode; + qreal m_pixelRatio; + + std::atomic_flag m_renderQueued = ATOMIC_FLAG_INIT; }; diff --git a/platform/qt/src/qmapboxgl_renderer_backend.cpp b/platform/qt/src/qmapboxgl_renderer_backend.cpp new file mode 100644 index 0000000000..917741f5ce --- /dev/null +++ b/platform/qt/src/qmapboxgl_renderer_backend.cpp @@ -0,0 +1,49 @@ +#include "qmapboxgl_renderer_backend.hpp" + +#include <QtGlobal> + +#if QT_VERSION >= 0x050000 +#include <QOpenGLContext> +#else +#include <QGLContext> +#endif + +void QMapboxGLRendererBackend::updateAssumedState() +{ + assumeFramebufferBinding(ImplicitFramebufferBinding); + assumeViewport(0, 0, m_size); +} + +void QMapboxGLRendererBackend::bind() +{ + assert(mbgl::BackendScope::exists()); + + setFramebufferBinding(m_fbo); + setViewport(0, 0, m_size); +} + +mbgl::Size QMapboxGLRendererBackend::getFramebufferSize() const +{ + return m_size; +} + +void QMapboxGLRendererBackend::updateFramebuffer(quint32 fbo, const mbgl::Size &size) +{ + m_fbo = fbo; + m_size = size; +} + +/*! + Initializes an OpenGL extension function such as Vertex Array Objects (VAOs), + required by Mapbox GL Native engine. +*/ +mbgl::gl::ProcAddress QMapboxGLRendererBackend::getExtensionFunctionPointer(const char* name) +{ +#if QT_VERSION >= 0x050000 + QOpenGLContext* thisContext = QOpenGLContext::currentContext(); + return thisContext->getProcAddress(name); +#else + const QGLContext* thisContext = QGLContext::currentContext(); + return reinterpret_cast<mbgl::gl::ProcAddress>(thisContext->getProcAddress(name)); +#endif +} diff --git a/platform/qt/src/qmapboxgl_renderer_backend.hpp b/platform/qt/src/qmapboxgl_renderer_backend.hpp new file mode 100644 index 0000000000..de66b035fc --- /dev/null +++ b/platform/qt/src/qmapboxgl_renderer_backend.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "qmapboxgl.hpp" + +#include <mbgl/renderer/renderer_backend.hpp> +#include <mbgl/storage/default_file_source.hpp> +#include <mbgl/util/shared_thread_pool.hpp> + +class QMapboxGLRendererBackend : public mbgl::RendererBackend +{ +public: + QMapboxGLRendererBackend() = default; + virtual ~QMapboxGLRendererBackend() = default; + + // mbgl::RendererBackend implementation + void updateAssumedState() final; + void bind() final; + mbgl::Size getFramebufferSize() const final; + + void updateFramebuffer(quint32 fbo, const mbgl::Size &); + +protected: + mbgl::gl::ProcAddress getExtensionFunctionPointer(const char*) final; + + // No-op, implicit mode. + void activate() final {} + void deactivate() final {} + +private: + quint32 m_fbo = 0; + mbgl::Size m_size = { 0, 0 }; + + Q_DISABLE_COPY(QMapboxGLRendererBackend) +}; diff --git a/platform/qt/src/qmapboxgl_renderer_frontend_p.cpp b/platform/qt/src/qmapboxgl_renderer_frontend_p.cpp deleted file mode 100644 index ea60851eb4..0000000000 --- a/platform/qt/src/qmapboxgl_renderer_frontend_p.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "qmapboxgl_renderer_frontend_p.hpp" - -#include <mbgl/renderer/backend_scope.hpp> -#include <mbgl/renderer/renderer.hpp> - -QMapboxGLRendererFrontend::QMapboxGLRendererFrontend(std::unique_ptr<mbgl::Renderer> renderer_, mbgl::RendererBackend& backend_) - : renderer(std::move(renderer_)) - , backend(backend_) { -} - -QMapboxGLRendererFrontend::~QMapboxGLRendererFrontend() = default; - -void QMapboxGLRendererFrontend::reset() { - if (renderer) { - renderer.reset(); - } -} - -void QMapboxGLRendererFrontend::update(std::shared_ptr<mbgl::UpdateParameters> updateParameters_) { - updateParameters = updateParameters_; - emit updated(); -} - -void QMapboxGLRendererFrontend::setObserver(mbgl::RendererObserver& observer_) { - if (!renderer) return; - - renderer->setObserver(&observer_); -} - -void QMapboxGLRendererFrontend::render() { - if (!renderer || !updateParameters) return; - - // The OpenGL implementation automatically enables the OpenGL context for us. - mbgl::BackendScope scope { backend, mbgl::BackendScope::ScopeType::Implicit }; - - renderer->render(*updateParameters); -} diff --git a/platform/qt/src/qmapboxgl_renderer_frontend_p.hpp b/platform/qt/src/qmapboxgl_renderer_frontend_p.hpp deleted file mode 100644 index c5e2bacc34..0000000000 --- a/platform/qt/src/qmapboxgl_renderer_frontend_p.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include <mbgl/renderer/renderer_backend.hpp> -#include <mbgl/renderer/renderer_frontend.hpp> - -#include <QObject> - -namespace mbgl { - class Renderer; -} // namespace mbgl - -class QMapboxGLRendererFrontend : public QObject, public mbgl::RendererFrontend -{ - Q_OBJECT - -public: - explicit QMapboxGLRendererFrontend(std::unique_ptr<mbgl::Renderer>, mbgl::RendererBackend&); - ~QMapboxGLRendererFrontend() override; - - void reset() override; - void setObserver(mbgl::RendererObserver&) override; - - void update(std::shared_ptr<mbgl::UpdateParameters>) override; - -public slots: - void render(); - -signals: - void updated(); - -private: - std::unique_ptr<mbgl::Renderer> renderer; - mbgl::RendererBackend& backend; - std::shared_ptr<mbgl::UpdateParameters> updateParameters; -}; diff --git a/platform/qt/src/qmapboxgl_renderer_observer.hpp b/platform/qt/src/qmapboxgl_renderer_observer.hpp new file mode 100644 index 0000000000..ee340113ff --- /dev/null +++ b/platform/qt/src/qmapboxgl_renderer_observer.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include <mbgl/actor/actor.hpp> +#include <mbgl/actor/actor_ref.hpp> +#include <mbgl/actor/mailbox.hpp> +#include <mbgl/renderer/renderer_observer.hpp> +#include <mbgl/util/run_loop.hpp> + +#include <memory> + +// Forwards RendererObserver signals to the given +// Delegate RendererObserver on the given RunLoop +class QMapboxGLRendererObserver : public mbgl::RendererObserver { +public: + QMapboxGLRendererObserver(mbgl::util::RunLoop& mapRunLoop, mbgl::RendererObserver& delegate_) + : mailbox(std::make_shared<mbgl::Mailbox>(mapRunLoop)) + , delegate(delegate_, mailbox) { + } + + ~QMapboxGLRendererObserver() { + mailbox->close(); + } + + void onInvalidate() final { + delegate.invoke(&mbgl::RendererObserver::onInvalidate); + } + + void onResourceError(std::exception_ptr err) final { + delegate.invoke(&mbgl::RendererObserver::onResourceError, err); + } + + void onWillStartRenderingMap() final { + delegate.invoke(&mbgl::RendererObserver::onWillStartRenderingMap); + } + + void onWillStartRenderingFrame() final { + delegate.invoke(&mbgl::RendererObserver::onWillStartRenderingFrame); + } + + void onDidFinishRenderingFrame(RenderMode mode, bool repaintNeeded) final { + delegate.invoke(&mbgl::RendererObserver::onDidFinishRenderingFrame, mode, repaintNeeded); + } + + void onDidFinishRenderingMap() final { + delegate.invoke(&mbgl::RendererObserver::onDidFinishRenderingMap); + } + +private: + std::shared_ptr<mbgl::Mailbox> mailbox; + mbgl::ActorRef<mbgl::RendererObserver> delegate; +}; diff --git a/platform/qt/src/qt_logging.cpp b/platform/qt/src/qt_logging.cpp index acbe9562d0..acbe9562d0 100755..100644 --- a/platform/qt/src/qt_logging.cpp +++ b/platform/qt/src/qt_logging.cpp diff --git a/platform/qt/test/qmapboxgl.test.cpp b/platform/qt/test/qmapboxgl.test.cpp index 932460b932..2a56b346a3 100644 --- a/platform/qt/test/qmapboxgl.test.cpp +++ b/platform/qt/test/qmapboxgl.test.cpp @@ -15,8 +15,8 @@ QMapboxGLTest::QMapboxGLTest() : size(512, 512), fbo((assert(widget.context()->i this, SLOT(onMapChanged(QMapboxGL::MapChange))); connect(&map, SIGNAL(needsRendering()), this, SLOT(onNeedsRendering())); - map.resize(fbo.size(), fbo.size()); - map.setFramebufferObject(fbo.handle()); + map.resize(fbo.size()); + map.setFramebufferObject(fbo.handle(), fbo.size()); map.setCoordinateZoom(QMapbox::Coordinate(60.170448, 24.942046), 14); } diff --git a/scripts/generate-shaders.js b/scripts/generate-shaders.js index 46c097d51a..b1eeffb8a0 100755 --- a/scripts/generate-shaders.js +++ b/scripts/generate-shaders.js @@ -7,9 +7,6 @@ const outputPath = 'src/mbgl/shaders'; var shaders = require('../mapbox-gl-js/src/shaders'); -delete shaders.heatmap; -delete shaders.heatmapTexture; - require('./style-code'); writeIfModified(path.join(outputPath, 'preludes.hpp'), `// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. diff --git a/scripts/generate-style-code.js b/scripts/generate-style-code.js index ff40244f98..6ddb787f19 100755 --- a/scripts/generate-style-code.js +++ b/scripts/generate-style-code.js @@ -97,6 +97,8 @@ global.paintPropertyType = function (property, type) { global.propertyValueType = function (property) { if (isDataDriven(property)) { return `DataDrivenPropertyValue<${evaluatedType(property)}>`; + } else if (property.name === 'heatmap-color') { + return `HeatmapColorPropertyValue`; } else { return `PropertyValue<${evaluatedType(property)}>`; } diff --git a/scripts/style-spec.js b/scripts/style-spec.js index 00daee70d0..196adc0b32 100644 --- a/scripts/style-spec.js +++ b/scripts/style-spec.js @@ -1,5 +1,3 @@ var spec = module.exports = require('../mapbox-gl-js/src/style-spec/reference/v8'); // Make temporary modifications here when Native doesn't have all features that JS has. - -delete spec.layer.type.values.heatmap; diff --git a/src/mbgl/annotation/render_annotation_source.cpp b/src/mbgl/annotation/render_annotation_source.cpp index 38ca5ccd0b..a237100d13 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -73,8 +73,8 @@ std::vector<Feature> RenderAnnotationSource::querySourceFeatures(const SourceQue return {}; } -void RenderAnnotationSource::onLowMemory() { - tilePyramid.onLowMemory(); +void RenderAnnotationSource::reduceMemoryUse() { + tilePyramid.reduceMemoryUse(); } void RenderAnnotationSource::dumpDebugLogs() const { diff --git a/src/mbgl/annotation/render_annotation_source.hpp b/src/mbgl/annotation/render_annotation_source.hpp index aa2578d4e3..e812ec2883 100644 --- a/src/mbgl/annotation/render_annotation_source.hpp +++ b/src/mbgl/annotation/render_annotation_source.hpp @@ -33,7 +33,7 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; - void onLowMemory() final; + void reduceMemoryUse() final; void dumpDebugLogs() const final; private: diff --git a/src/mbgl/geometry/dem_data.cpp b/src/mbgl/geometry/dem_data.cpp index 78134dadc1..7fa98950ea 100644 --- a/src/mbgl/geometry/dem_data.cpp +++ b/src/mbgl/geometry/dem_data.cpp @@ -3,7 +3,7 @@ namespace mbgl { -DEMData::DEMData(const PremultipliedImage& _image): +DEMData::DEMData(const PremultipliedImage& _image, Tileset::DEMEncoding encoding): dim(_image.size.height), border(std::max<int32_t>(std::ceil(_image.size.height / 2), 1)), stride(dim + 2 * border), @@ -13,13 +13,25 @@ DEMData::DEMData(const PremultipliedImage& _image): throw std::runtime_error("raster-dem tiles must be square."); } + auto decodeMapbox = [] (const uint8_t r, const uint8_t g, const uint8_t b){ + // https://www.mapbox.com/help/access-elevation-data/#mapbox-terrain-rgb + return (r * 256 * 256 + g * 256 + b)/10 - 10000; + }; + + auto decodeTerrarium = [] (const uint8_t r, const uint8_t g, const uint8_t b){ + // https://aws.amazon.com/public-datasets/terrain/ + return ((r * 256 + g + b / 256) - 32768); + }; + + auto decodeRGB = encoding == Tileset::DEMEncoding::Terrarium ? decodeTerrarium : decodeMapbox; + std::memset(image.data.get(), 0, image.bytes()); for (int32_t y = 0; y < dim; y++) { for (int32_t x = 0; x < dim; x++) { const int32_t i = y * dim + x; const int32_t j = i * 4; - set(x, y, (_image.data[j] * 256 * 256 + _image.data[j+1] * 256 + _image.data[j+2])/10 - 10000); + set(x, y, decodeRGB(_image.data[j], _image.data[j+1], _image.data[j+2])); } } diff --git a/src/mbgl/geometry/dem_data.hpp b/src/mbgl/geometry/dem_data.hpp index 507a51661d..817d3cc9c9 100644 --- a/src/mbgl/geometry/dem_data.hpp +++ b/src/mbgl/geometry/dem_data.hpp @@ -2,6 +2,7 @@ #include <mbgl/math/clamp.hpp> #include <mbgl/util/image.hpp> +#include <mbgl/util/tileset.hpp> #include <memory> #include <array> @@ -12,7 +13,7 @@ namespace mbgl { class DEMData { public: - DEMData(const PremultipliedImage& image); + DEMData(const PremultipliedImage& image, Tileset::DEMEncoding encoding); void backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy); void set(const int32_t x, const int32_t y, const int32_t value) { diff --git a/src/mbgl/gl/color_mode.hpp b/src/mbgl/gl/color_mode.hpp index c6594a3a77..e394f43501 100644 --- a/src/mbgl/gl/color_mode.hpp +++ b/src/mbgl/gl/color_mode.hpp @@ -85,6 +85,10 @@ public: static ColorMode alphaBlended() { return ColorMode { Add { One, OneMinusSrcAlpha }, {}, { true, true, true, true } }; } + + static ColorMode additive() { + return ColorMode { Add { One, One }, {}, { true, true, true, true } }; + } }; constexpr bool operator!=(const ColorMode::Mask& a, const ColorMode::Mask& b) { diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index b6244d58c7..f40cfa1f2c 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -60,6 +60,16 @@ static_assert(std::is_same<std::underlying_type_t<TextureFormat>, GLenum>::value static_assert(underlying_type(TextureFormat::RGBA) == GL_RGBA, "OpenGL type mismatch"); static_assert(underlying_type(TextureFormat::Alpha) == GL_ALPHA, "OpenGL type mismatch"); +static_assert(std::is_same<std::underlying_type_t<TextureType>, GLenum>::value, "OpenGL type mismatch"); +static_assert(underlying_type(TextureType::UnsignedByte) == GL_UNSIGNED_BYTE, "OpenGL type mismatch"); + +#if MBGL_USE_GLES2 && GL_HALF_FLOAT_OES +static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_OES, "OpenGL type mismatch"); +#endif +#if !MBGL_USE_GLES2 && GL_HALF_FLOAT_ARB +static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_ARB, "OpenGL type mismatch"); +#endif + static_assert(underlying_type(UniformDataType::Float) == GL_FLOAT, "OpenGL type mismatch"); static_assert(underlying_type(UniformDataType::FloatVec2) == GL_FLOAT_VEC2, "OpenGL type mismatch"); static_assert(underlying_type(UniformDataType::FloatVec3) == GL_FLOAT_VEC3, "OpenGL type mismatch"); @@ -116,6 +126,19 @@ void Context::initializeExtensions(const std::function<gl::ProcAddress(const cha programBinary = std::make_unique<extension::ProgramBinary>(fn); #endif +#if MBGL_USE_GLES2 + constexpr const char* halfFloatExtensionName = "OES_texture_half_float"; + constexpr const char* halfFloatColorBufferExtensionName = "EXT_color_buffer_half_float"; +#else + constexpr const char* halfFloatExtensionName = "ARB_half_float_pixel"; + constexpr const char* halfFloatColorBufferExtensionName = "ARB_color_buffer_float"; +#endif + if (strstr(extensions, halfFloatExtensionName) != nullptr && + strstr(extensions, halfFloatColorBufferExtensionName) != nullptr) { + + supportsHalfFloatTextures = true; + } + if (!supportsVertexArrays()) { Log::Warning(Event::OpenGL, "Not using Vertex Array Objects"); } @@ -491,10 +514,10 @@ Context::createFramebuffer(const Texture& color, } UniqueTexture -Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit) { +Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) { auto obj = createTexture(); pixelStoreUnpack = { 1 }; - updateTexture(obj, size, data, format, unit); + updateTexture(obj, size, data, format, unit, type); // We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures. // We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus. MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); @@ -505,11 +528,11 @@ Context::createTexture(const Size size, const void* data, TextureFormat format, } void Context::updateTexture( - TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit) { + TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) { activeTextureUnit = unit; texture[unit] = id; MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, static_cast<GLenum>(format), size.width, - size.height, 0, static_cast<GLenum>(format), GL_UNSIGNED_BYTE, + size.height, 0, static_cast<GLenum>(format), static_cast<GLenum>(type), data)); } diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 14f078367f..67624288e2 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -125,23 +125,29 @@ public: // Create a texture from an image with data. template <typename Image> - Texture createTexture(const Image& image, TextureUnit unit = 0) { + Texture createTexture(const Image& image, + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - return { image.size, createTexture(image.size, image.data.get(), format, unit) }; + return { image.size, createTexture(image.size, image.data.get(), format, unit, type) }; } template <typename Image> - void updateTexture(Texture& obj, const Image& image, TextureUnit unit = 0) { + void updateTexture(Texture& obj, + const Image& image, + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit); + updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit, type); obj.size = image.size; } // Creates an empty texture with the specified dimensions. Texture createTexture(const Size size, TextureFormat format = TextureFormat::RGBA, - TextureUnit unit = 0) { - return { size, createTexture(size, nullptr, format, unit) }; + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { + return { size, createTexture(size, nullptr, format, unit, type) }; } void bindTexture(Texture&, @@ -232,6 +238,8 @@ public: State<value::PixelTransferStencil> pixelTransferStencil; #endif // MBGL_USE_GLES2 + bool supportsHalfFloatTextures = false; + private: State<value::StencilFunc> stencilFunc; State<value::StencilMask> stencilMask; @@ -259,8 +267,8 @@ private: void updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); UniqueBuffer createIndexBuffer(const void* data, std::size_t size, const BufferUsage usage); void updateIndexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); - UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit); - void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit); + UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit, TextureType); + void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit, TextureType); UniqueFramebuffer createFramebuffer(); UniqueRenderbuffer createRenderbuffer(RenderbufferType, Size size); std::unique_ptr<uint8_t[]> readFramebuffer(Size, TextureFormat, bool flip); diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index da08195e58..376a784a0c 100644 --- a/src/mbgl/gl/types.hpp +++ b/src/mbgl/gl/types.hpp @@ -64,6 +64,15 @@ enum class TextureFormat : uint32_t { #endif // MBGL_USE_GLES2 }; +enum class TextureType : uint32_t { + UnsignedByte = 0x1401, +#if MBGL_USE_GLES2 + HalfFloat = 0x8D61, +#else + HalfFloat = 0x140B, +#endif // MBGL_USE_GLES2 +}; + enum class PrimitiveType { Points = 0x0000, Lines = 0x0001, diff --git a/src/mbgl/programs/attributes.hpp b/src/mbgl/programs/attributes.hpp index 5d7a6474cf..c677c84d5d 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -30,7 +30,7 @@ MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos); MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_normal_ed); MBGL_DEFINE_ATTRIBUTE(uint8_t, 1, a_fade_opacity); -MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); +MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); template <typename T, std::size_t N> struct a_data { @@ -142,6 +142,11 @@ struct a_halo_blur { using Type = gl::Attribute<float, 1>; }; +struct a_weight { + static auto name() { return "a_weight"; } + using Type = gl::Attribute<float, 1>; +}; + } // namespace attributes struct PositionOnlyLayoutAttributes : gl::Attributes< diff --git a/src/mbgl/programs/collision_box_program.hpp b/src/mbgl/programs/collision_box_program.hpp index 8d712a3df3..6e75adf36e 100644 --- a/src/mbgl/programs/collision_box_program.hpp +++ b/src/mbgl/programs/collision_box_program.hpp @@ -110,6 +110,7 @@ class CollisionCircleProgram : public Program< gl::Uniforms< uniforms::u_matrix, uniforms::u_extrude_scale, + uniforms::u_overscale_factor, uniforms::u_camera_to_center_distance>, style::Properties<>> { diff --git a/src/mbgl/programs/heatmap_program.cpp b/src/mbgl/programs/heatmap_program.cpp new file mode 100644 index 0000000000..67f84fbd52 --- /dev/null +++ b/src/mbgl/programs/heatmap_program.cpp @@ -0,0 +1,7 @@ +#include <mbgl/programs/heatmap_program.hpp> + +namespace mbgl { + +static_assert(sizeof(HeatmapLayoutVertex) == 4, "expected HeatmapLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_program.hpp b/src/mbgl/programs/heatmap_program.hpp new file mode 100644 index 0000000000..2d9b80404f --- /dev/null +++ b/src/mbgl/programs/heatmap_program.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/heatmap.hpp> +#include <mbgl/util/geometry.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(float, u_intensity); +} // namespace uniforms + +class HeatmapProgram : public Program< + shaders::heatmap, + gl::Triangle, + gl::Attributes< + attributes::a_pos>, + gl::Uniforms< + uniforms::u_intensity, + uniforms::u_matrix, + uniforms::heatmap::u_extrude_scale>, + style::HeatmapPaintProperties> +{ +public: + using Program::Program; + + /* + * @param {number} x vertex position + * @param {number} y vertex position + * @param {number} ex extrude normal + * @param {number} ey extrude normal + */ + static LayoutVertex vertex(Point<int16_t> p, float ex, float ey) { + return LayoutVertex { + {{ + static_cast<int16_t>((p.x * 2) + ((ex + 1) / 2)), + static_cast<int16_t>((p.y * 2) + ((ey + 1) / 2)) + }} + }; + } +}; + +using HeatmapLayoutVertex = HeatmapProgram::LayoutVertex; +using HeatmapAttributes = HeatmapProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_texture_program.cpp b/src/mbgl/programs/heatmap_texture_program.cpp new file mode 100644 index 0000000000..3b0e24eab8 --- /dev/null +++ b/src/mbgl/programs/heatmap_texture_program.cpp @@ -0,0 +1,7 @@ +#include <mbgl/programs/heatmap_texture_program.hpp> + +namespace mbgl { + +static_assert(sizeof(HeatmapTextureLayoutVertex) == 4, "expected HeatmapTextureLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_texture_program.hpp b/src/mbgl/programs/heatmap_texture_program.hpp new file mode 100644 index 0000000000..7afe8060d0 --- /dev/null +++ b/src/mbgl/programs/heatmap_texture_program.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include <mbgl/programs/program.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> +#include <mbgl/shaders/heatmap_texture.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/util/geometry.hpp> + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_color_ramp); +} // namespace uniforms + +class HeatmapTextureProgram : public Program< + shaders::heatmap_texture, + gl::Triangle, + gl::Attributes<attributes::a_pos>, + gl::Uniforms< + uniforms::u_matrix, + uniforms::u_world, + uniforms::u_image, + uniforms::u_color_ramp, + uniforms::u_opacity>, + style::Properties<>> { +public: + using Program::Program; + + static LayoutVertex layoutVertex(Point<int16_t> p) { + return LayoutVertex{ + {{ + p.x, + p.y + }} + }; + } +}; + +using HeatmapTextureLayoutVertex = HeatmapTextureProgram::LayoutVertex; +using HeatmapTextureAttributes = HeatmapTextureProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/hillshade_prepare_program.hpp b/src/mbgl/programs/hillshade_prepare_program.hpp index 0f31a22df5..76638afddd 100644 --- a/src/mbgl/programs/hillshade_prepare_program.hpp +++ b/src/mbgl/programs/hillshade_prepare_program.hpp @@ -10,6 +10,7 @@ namespace mbgl { namespace uniforms { MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_dimension); +MBGL_DEFINE_UNIFORM_SCALAR(float, u_maxzoom); } // namespace uniforms class HillshadePrepareProgram : public Program< @@ -22,6 +23,7 @@ class HillshadePrepareProgram : public Program< uniforms::u_matrix, uniforms::u_dimension, uniforms::u_zoom, + uniforms::u_maxzoom, uniforms::u_image>, style::Properties<>> { public: diff --git a/src/mbgl/programs/programs.hpp b/src/mbgl/programs/programs.hpp index f533a6f633..b703323d9c 100644 --- a/src/mbgl/programs/programs.hpp +++ b/src/mbgl/programs/programs.hpp @@ -6,6 +6,8 @@ #include <mbgl/programs/extrusion_texture_program.hpp> #include <mbgl/programs/fill_program.hpp> #include <mbgl/programs/fill_extrusion_program.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/programs/heatmap_texture_program.hpp> #include <mbgl/programs/hillshade_program.hpp> #include <mbgl/programs/hillshade_prepare_program.hpp> #include <mbgl/programs/line_program.hpp> @@ -30,6 +32,8 @@ public: fillPattern(context, programParameters), fillOutline(context, programParameters), fillOutlinePattern(context, programParameters), + heatmap(context, programParameters), + heatmapTexture(context, programParameters), hillshade(context, programParameters), hillshadePrepare(context, programParameters), line(context, programParameters), @@ -55,6 +59,8 @@ public: ProgramMap<FillPatternProgram> fillPattern; ProgramMap<FillOutlineProgram> fillOutline; ProgramMap<FillOutlinePatternProgram> fillOutlinePattern; + ProgramMap<HeatmapProgram> heatmap; + HeatmapTextureProgram heatmapTexture; HillshadeProgram hillshade; HillshadePrepareProgram hillshadePrepare; ProgramMap<LineProgram> line; diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp index 184f42e504..071a27c808 100644 --- a/src/mbgl/programs/uniforms.hpp +++ b/src/mbgl/programs/uniforms.hpp @@ -37,9 +37,14 @@ MBGL_DEFINE_UNIFORM_SCALAR(Size, u_texsize); MBGL_DEFINE_UNIFORM_SCALAR(bool, u_pitch_with_map); MBGL_DEFINE_UNIFORM_SCALAR(float, u_camera_to_center_distance); MBGL_DEFINE_UNIFORM_SCALAR(float, u_fade_change); +MBGL_DEFINE_UNIFORM_SCALAR(float, u_weight); MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_extrude_scale); +namespace heatmap { +MBGL_DEFINE_UNIFORM_SCALAR(float, u_extrude_scale); +} // namespace heatmap + MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_a); MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_br_a); MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_pattern_tl_b); @@ -55,6 +60,7 @@ MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_fadetexture); MBGL_DEFINE_UNIFORM_SCALAR(float, u_scale_a); MBGL_DEFINE_UNIFORM_SCALAR(float, u_scale_b); MBGL_DEFINE_UNIFORM_SCALAR(float, u_tile_units_to_pixels); +MBGL_DEFINE_UNIFORM_SCALAR(float, u_overscale_factor); } // namespace uniforms } // namespace mbgl diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.cpp b/src/mbgl/renderer/buckets/heatmap_bucket.cpp new file mode 100644 index 0000000000..a185e04ad2 --- /dev/null +++ b/src/mbgl/renderer/buckets/heatmap_bucket.cpp @@ -0,0 +1,98 @@ +#include <mbgl/renderer/buckets/heatmap_bucket.hpp> +#include <mbgl/renderer/bucket_parameters.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/math.hpp> + +namespace mbgl { + +using namespace style; + +HeatmapBucket::HeatmapBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers) + : mode(parameters.mode) { + for (const auto& layer : layers) { + paintPropertyBinders.emplace( + std::piecewise_construct, + std::forward_as_tuple(layer->getID()), + std::forward_as_tuple( + layer->as<RenderHeatmapLayer>()->evaluated, + parameters.tileID.overscaledZ)); + } +} + +void HeatmapBucket::upload(gl::Context& context) { + vertexBuffer = context.createVertexBuffer(std::move(vertices)); + indexBuffer = context.createIndexBuffer(std::move(triangles)); + + for (auto& pair : paintPropertyBinders) { + pair.second.upload(context); + } + + uploaded = true; +} + +bool HeatmapBucket::hasData() const { + return !segments.empty(); +} + +void HeatmapBucket::addFeature(const GeometryTileFeature& feature, + const GeometryCollection& geometry) { + constexpr const uint16_t vertexLength = 4; + + for (auto& points : geometry) { + for(auto& point : points) { + auto x = point.x; + auto y = point.y; + + // Do not include points that are outside the tile boundaries. + // Include all points in Still mode. You need to include points from + // neighbouring tiles so that they are not clipped at tile boundaries. + if ((mode == MapMode::Continuous) && + (x < 0 || x >= util::EXTENT || y < 0 || y >= util::EXTENT)) continue; + + if (segments.empty() || segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) { + // Move to a new segments because the old one can't hold the geometry. + segments.emplace_back(vertices.vertexSize(), triangles.indexSize()); + } + + // this geometry will be of the Point type, and we'll derive + // two triangles from it. + // + // ┌─────────┐ + // │ 4 3 │ + // │ │ + // │ 1 2 │ + // └─────────┘ + // + vertices.emplace_back(HeatmapProgram::vertex(point, -1, -1)); // 1 + vertices.emplace_back(HeatmapProgram::vertex(point, 1, -1)); // 2 + vertices.emplace_back(HeatmapProgram::vertex(point, 1, 1)); // 3 + vertices.emplace_back(HeatmapProgram::vertex(point, -1, 1)); // 4 + + auto& segment = segments.back(); + assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max()); + uint16_t index = segment.vertexLength; + + // 1, 2, 3 + // 1, 4, 3 + triangles.emplace_back(index, index + 1, index + 2); + triangles.emplace_back(index, index + 3, index + 2); + + segment.vertexLength += vertexLength; + segment.indexLength += 6; + } + } + + for (auto& pair : paintPropertyBinders) { + pair.second.populateVertexVectors(feature, vertices.vertexSize()); + } +} + +float HeatmapBucket::getQueryRadius(const RenderLayer& layer) const { + (void)layer; + return 0; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.hpp b/src/mbgl/renderer/buckets/heatmap_bucket.hpp new file mode 100644 index 0000000000..3b9f1edb81 --- /dev/null +++ b/src/mbgl/renderer/buckets/heatmap_bucket.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include <mbgl/renderer/bucket.hpp> +#include <mbgl/map/mode.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/gl/vertex_buffer.hpp> +#include <mbgl/gl/index_buffer.hpp> +#include <mbgl/programs/segment.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +namespace mbgl { + +class BucketParameters; + +class HeatmapBucket : public Bucket { +public: + HeatmapBucket(const BucketParameters&, const std::vector<const RenderLayer*>&); + + void addFeature(const GeometryTileFeature&, + const GeometryCollection&) override; + bool hasData() const override; + + void upload(gl::Context&) override; + + float getQueryRadius(const RenderLayer&) const override; + + gl::VertexVector<HeatmapLayoutVertex> vertices; + gl::IndexVector<gl::Triangles> triangles; + SegmentVector<HeatmapAttributes> segments; + + optional<gl::VertexBuffer<HeatmapLayoutVertex>> vertexBuffer; + optional<gl::IndexBuffer<gl::Triangles>> indexBuffer; + + std::map<std::string, HeatmapProgram::PaintPropertyBinders> paintPropertyBinders; + + const MapMode mode; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.cpp b/src/mbgl/renderer/buckets/hillshade_bucket.cpp index 8011681ee0..00b9536894 100644 --- a/src/mbgl/renderer/buckets/hillshade_bucket.cpp +++ b/src/mbgl/renderer/buckets/hillshade_bucket.cpp @@ -8,7 +8,7 @@ namespace mbgl { using namespace style; -HillshadeBucket::HillshadeBucket(PremultipliedImage&& image_): demdata(image_) { +HillshadeBucket::HillshadeBucket(PremultipliedImage&& image_, Tileset::DEMEncoding encoding): demdata(image_, encoding) { } HillshadeBucket::HillshadeBucket(DEMData&& demdata_) : demdata(std::move(demdata_)) { diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.hpp b/src/mbgl/renderer/buckets/hillshade_bucket.hpp index 3d9f6c61af..5335f7ceda 100644 --- a/src/mbgl/renderer/buckets/hillshade_bucket.hpp +++ b/src/mbgl/renderer/buckets/hillshade_bucket.hpp @@ -8,6 +8,7 @@ #include <mbgl/renderer/bucket.hpp> #include <mbgl/renderer/tile_mask.hpp> #include <mbgl/geometry/dem_data.hpp> +#include <mbgl/util/tileset.hpp> #include <mbgl/util/image.hpp> #include <mbgl/util/mat4.hpp> #include <mbgl/util/optional.hpp> @@ -16,8 +17,8 @@ namespace mbgl { class HillshadeBucket : public Bucket { public: - HillshadeBucket(PremultipliedImage&&); - HillshadeBucket(std::shared_ptr<PremultipliedImage>); + HillshadeBucket(PremultipliedImage&&, Tileset::DEMEncoding encoding); + HillshadeBucket(std::shared_ptr<PremultipliedImage>, Tileset::DEMEncoding encoding); HillshadeBucket(DEMData&&); diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp new file mode 100644 index 0000000000..0f9e3239ef --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -0,0 +1,178 @@ +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> +#include <mbgl/renderer/buckets/heatmap_bucket.hpp> +#include <mbgl/renderer/render_tile.hpp> +#include <mbgl/renderer/paint_parameters.hpp> +#include <mbgl/renderer/render_static_data.hpp> +#include <mbgl/programs/programs.hpp> +#include <mbgl/programs/heatmap_program.hpp> +#include <mbgl/tile/tile.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/geometry/feature_index.hpp> +#include <mbgl/util/math.hpp> +#include <mbgl/util/intersection_tests.hpp> + +namespace mbgl { + +using namespace style; + +RenderHeatmapLayer::RenderHeatmapLayer(Immutable<style::HeatmapLayer::Impl> _impl) + : RenderLayer(style::LayerType::Heatmap, _impl), + unevaluated(impl().paint.untransitioned()), colorRamp({256, 1}) { +} + +const style::HeatmapLayer::Impl& RenderHeatmapLayer::impl() const { + return static_cast<const style::HeatmapLayer::Impl&>(*baseImpl); +} + +std::unique_ptr<Bucket> RenderHeatmapLayer::createBucket(const BucketParameters& parameters, const std::vector<const RenderLayer*>& layers) const { + return std::make_unique<HeatmapBucket>(parameters, layers); +} + +void RenderHeatmapLayer::transition(const TransitionParameters& parameters) { + unevaluated = impl().paint.transitioned(parameters, std::move(unevaluated)); +} + +void RenderHeatmapLayer::evaluate(const PropertyEvaluationParameters& parameters) { + evaluated = unevaluated.evaluate(parameters); + + passes = (evaluated.get<style::HeatmapOpacity>() > 0) + ? (RenderPass::Translucent | RenderPass::Pass3D) + : RenderPass::None; +} + +bool RenderHeatmapLayer::hasTransition() const { + return unevaluated.hasTransition(); +} + +void RenderHeatmapLayer::render(PaintParameters& parameters, RenderSource*) { + if (parameters.pass == RenderPass::Opaque) { + return; + } + + if (parameters.pass == RenderPass::Pass3D) { + const auto& viewportSize = parameters.staticData.backendSize; + const auto size = Size{viewportSize.width / 4, viewportSize.height / 4}; + + if (!renderTexture || renderTexture->getSize() != size) { + if (parameters.context.supportsHalfFloatTextures) { + renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::HalfFloat); + + try { + renderTexture->bind(); + } catch (const std::runtime_error& ex) { + // can't render to a half-float texture; falling back to unsigned byte one + renderTexture = nullopt; + parameters.context.supportsHalfFloatTextures = false; + } + } + + if (!renderTexture) { + renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::UnsignedByte); + renderTexture->bind(); + } + + } else { + renderTexture->bind(); + } + + if (!colorRampTexture) { + colorRampTexture = parameters.context.createTexture(colorRamp, 1, gl::TextureType::UnsignedByte); + } + + parameters.context.clear(Color{ 0.0f, 0.0f, 0.0f, 1.0f }, {}, {}); + + for (const RenderTile& tile : renderTiles) { + assert(dynamic_cast<HeatmapBucket*>(tile.tile.getBucket(*baseImpl))); + HeatmapBucket& bucket = *reinterpret_cast<HeatmapBucket*>(tile.tile.getBucket(*baseImpl)); + + const auto extrudeScale = tile.id.pixelsToTileUnits(1, parameters.state.getZoom()); + + const auto stencilMode = parameters.mapMode != MapMode::Continuous + ? parameters.stencilModeForClipping(tile.clip) + : gl::StencilMode::disabled(); + + parameters.programs.heatmap.get(evaluated).draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + stencilMode, + gl::ColorMode::additive(), + HeatmapProgram::UniformValues { + uniforms::u_intensity::Value{evaluated.get<style::HeatmapIntensity>()}, + uniforms::u_matrix::Value{tile.matrix}, + uniforms::heatmap::u_extrude_scale::Value{extrudeScale} + }, + *bucket.vertexBuffer, + *bucket.indexBuffer, + bucket.segments, + bucket.paintPropertyBinders.at(getID()), + evaluated, + parameters.state.getZoom(), + getID() + ); + } + + } else if (parameters.pass == RenderPass::Translucent) { + parameters.context.bindTexture(renderTexture->getTexture(), 0, gl::TextureFilter::Linear); + parameters.context.bindTexture(*colorRampTexture, 1, gl::TextureFilter::Linear); + + const auto& size = parameters.staticData.backendSize; + + mat4 viewportMat; + matrix::ortho(viewportMat, 0, size.width, size.height, 0, 0, 1); + + const Properties<>::PossiblyEvaluated properties; + + parameters.programs.heatmapTexture.draw( + parameters.context, gl::Triangles(), gl::DepthMode::disabled(), + gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), + HeatmapTextureProgram::UniformValues{ + uniforms::u_matrix::Value{ viewportMat }, uniforms::u_world::Value{ size }, + uniforms::u_image::Value{ 0 }, + uniforms::u_color_ramp::Value{ 1 }, + uniforms::u_opacity::Value{ evaluated.get<HeatmapOpacity>() } }, + parameters.staticData.extrusionTextureVertexBuffer, + parameters.staticData.quadTriangleIndexBuffer, + parameters.staticData.extrusionTextureSegments, + HeatmapTextureProgram::PaintPropertyBinders{ properties, 0 }, properties, + parameters.state.getZoom(), getID()); + } +} + +void RenderHeatmapLayer::updateColorRamp() { + auto colorValue = unevaluated.get<HeatmapColor>().getValue(); + if (colorValue.isUndefined()) { + colorValue = HeatmapLayer::getDefaultHeatmapColor(); + } + + const auto length = colorRamp.bytes(); + + for (uint32_t i = 0; i < length; i += 4) { + const auto color = colorValue.evaluate(static_cast<double>(i) / length); + colorRamp.data[i + 0] = std::floor(color.r * 255); + colorRamp.data[i + 1] = std::floor(color.g * 255); + colorRamp.data[i + 2] = std::floor(color.b * 255); + colorRamp.data[i + 3] = std::floor(color.a * 255); + } + + if (colorRampTexture) { + colorRampTexture = nullopt; + } +} + +bool RenderHeatmapLayer::queryIntersectsFeature( + const GeometryCoordinates& queryGeometry, + const GeometryTileFeature& feature, + const float zoom, + const float bearing, + const float pixelsToTileUnits) const { + (void) queryGeometry; + (void) feature; + (void) zoom; + (void) bearing; + (void) pixelsToTileUnits; + return false; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.hpp b/src/mbgl/renderer/layers/render_heatmap_layer.hpp new file mode 100644 index 0000000000..3f0b1f91b4 --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include <mbgl/renderer/render_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> +#include <mbgl/util/optional.hpp> +#include <mbgl/util/offscreen_texture.hpp> + +namespace mbgl { + +class RenderHeatmapLayer: public RenderLayer { +public: + RenderHeatmapLayer(Immutable<style::HeatmapLayer::Impl>); + ~RenderHeatmapLayer() final = default; + + void transition(const TransitionParameters&) override; + void evaluate(const PropertyEvaluationParameters&) override; + bool hasTransition() const override; + void render(PaintParameters&, RenderSource*) override; + + bool queryIntersectsFeature( + const GeometryCoordinates&, + const GeometryTileFeature&, + const float, + const float, + const float) const override; + + void updateColorRamp(); + + std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; + + // Paint properties + style::HeatmapPaintProperties::Unevaluated unevaluated; + style::HeatmapPaintProperties::PossiblyEvaluated evaluated; + + const style::HeatmapLayer::Impl& impl() const; + + PremultipliedImage colorRamp; + optional<OffscreenTexture> renderTexture; + optional<gl::Texture> colorRampTexture; +}; + +template <> +inline bool RenderLayer::is<RenderHeatmapLayer>() const { + return type == style::LayerType::Heatmap; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index 7a767522c0..bcfd4ffe99 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -1,6 +1,7 @@ #include <mbgl/renderer/layers/render_hillshade_layer.hpp> #include <mbgl/renderer/buckets/hillshade_bucket.hpp> #include <mbgl/renderer/render_tile.hpp> +#include <mbgl/renderer/sources/render_raster_dem_source.hpp> #include <mbgl/renderer/paint_parameters.hpp> #include <mbgl/renderer/render_static_data.hpp> #include <mbgl/programs/programs.hpp> @@ -55,10 +56,14 @@ bool RenderHillshadeLayer::hasTransition() const { return unevaluated.hasTransition(); } -void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) { +void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource* src) { if (parameters.pass != RenderPass::Translucent && parameters.pass != RenderPass::Pass3D) return; + RenderRasterDEMSource* demsrc = dynamic_cast<RenderRasterDEMSource*>(src); + const uint8_t TERRAIN_RGB_MAXZOOM = 15; + const uint8_t maxzoom = demsrc != nullptr ? demsrc->getMaxZoom() : TERRAIN_RGB_MAXZOOM; + auto draw = [&] (const mat4& matrix, const auto& vertexBuffer, const auto& indexBuffer, @@ -118,6 +123,7 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) { uniforms::u_matrix::Value { mat }, uniforms::u_dimension::Value { {{uint16_t(tilesize * 2), uint16_t(tilesize * 2) }} }, uniforms::u_zoom::Value{ float(tile.id.canonical.z) }, + uniforms::u_maxzoom::Value{ float(maxzoom) }, uniforms::u_image::Value{ 0 } }, parameters.staticData.rasterVertexBuffer, @@ -136,14 +142,14 @@ void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) { if (bucket.vertexBuffer && bucket.indexBuffer && !bucket.segments.empty()) { // Draw only the parts of the tile that aren't drawn by another tile in the layer. - draw(tile.matrix, + draw(parameters.matrixForTile(tile.id, true), *bucket.vertexBuffer, *bucket.indexBuffer, bucket.segments, tile.id); } else { // Draw the full tile. - draw(tile.matrix, + draw(parameters.matrixForTile(tile.id, true), parameters.staticData.rasterVertexBuffer, parameters.staticData.quadTriangleIndexBuffer, parameters.staticData.rasterSegments, diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.hpp b/src/mbgl/renderer/layers/render_hillshade_layer.hpp index e9b9db1ec3..13093ee7ef 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.hpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.hpp @@ -16,7 +16,7 @@ public: void evaluate(const PropertyEvaluationParameters&) override; bool hasTransition() const override; - void render(PaintParameters&, RenderSource*) override; + void render(PaintParameters&, RenderSource* src) override; std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 04fcb2c3ab..9e493003c0 100644 --- a/src/mbgl/renderer/layers/render_symbol_layer.cpp +++ b/src/mbgl/renderer/layers/render_symbol_layer.cpp @@ -268,9 +268,10 @@ void RenderSymbolLayer::render(PaintParameters& parameters, RenderSource*) { gl::DepthMode::disabled(), gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), - CollisionBoxProgram::UniformValues { + CollisionCircleProgram::UniformValues { uniforms::u_matrix::Value{ tile.matrix }, uniforms::u_extrude_scale::Value{ extrudeScale }, + uniforms::u_overscale_factor::Value{ float(tile.tile.id.overscaleFactor()) }, uniforms::u_camera_to_center_distance::Value{ parameters.state.getCameraToCenterDistance() } }, *bucket.collisionCircle.vertexBuffer, diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp index 248905f834..bcdc175f14 100644 --- a/src/mbgl/renderer/render_layer.cpp +++ b/src/mbgl/renderer/render_layer.cpp @@ -8,6 +8,7 @@ #include <mbgl/renderer/layers/render_line_layer.hpp> #include <mbgl/renderer/layers/render_raster_layer.hpp> #include <mbgl/renderer/layers/render_symbol_layer.hpp> +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> #include <mbgl/style/types.hpp> #include <mbgl/renderer/render_tile.hpp> @@ -35,6 +36,8 @@ std::unique_ptr<RenderLayer> RenderLayer::create(Immutable<Layer::Impl> impl) { return std::make_unique<RenderCustomLayer>(staticImmutableCast<CustomLayer::Impl>(impl)); case LayerType::FillExtrusion: return std::make_unique<RenderFillExtrusionLayer>(staticImmutableCast<FillExtrusionLayer::Impl>(impl)); + case LayerType::Heatmap: + return std::make_unique<RenderHeatmapLayer>(staticImmutableCast<HeatmapLayer::Impl>(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index db88230e53..53519c763e 100644 --- a/src/mbgl/renderer/render_source.hpp +++ b/src/mbgl/renderer/render_source.hpp @@ -70,7 +70,7 @@ public: virtual std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const = 0; - virtual void onLowMemory() = 0; + virtual void reduceMemoryUse() = 0; virtual void dumpDebugLogs() const = 0; diff --git a/src/mbgl/renderer/renderer.cpp b/src/mbgl/renderer/renderer.cpp index 6d086c70b1..1d2f2bb522 100644 --- a/src/mbgl/renderer/renderer.cpp +++ b/src/mbgl/renderer/renderer.cpp @@ -94,9 +94,9 @@ void Renderer::dumpDebugLogs() { impl->dumDebugLogs(); } -void Renderer::onLowMemory() { +void Renderer::reduceMemoryUse() { BackendScope guard { impl->backend }; - impl->onLowMemory(); + impl->reduceMemoryUse(); } } // namespace mbgl diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 61e7d17242..2ac714e122 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -14,6 +14,7 @@ #include <mbgl/renderer/layers/render_background_layer.hpp> #include <mbgl/renderer/layers/render_custom_layer.hpp> #include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp> +#include <mbgl/renderer/layers/render_heatmap_layer.hpp> #include <mbgl/renderer/layers/render_hillshade_layer.hpp> #include <mbgl/renderer/style_diff.hpp> #include <mbgl/renderer/query.hpp> @@ -185,6 +186,10 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (layerAdded || layerChanged) { layer.transition(transitionParameters); + + if (layer.is<RenderHeatmapLayer>()) { + layer.as<RenderHeatmapLayer>()->updateColorRamp(); + } } if (layerAdded || layerChanged || zoomChanged || layer.hasTransition()) { @@ -290,7 +295,11 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { RenderLayer* layer = getRenderLayer(layerImpl->id); assert(layer); - if (!parameters.staticData.has3D && (layer->is<RenderFillExtrusionLayer>() || layer->is<RenderHillshadeLayer>())) { + if (!parameters.staticData.has3D && ( + layer->is<RenderFillExtrusionLayer>() || + layer->is<RenderHillshadeLayer>() || + layer->is<RenderHeatmapLayer>())) { + parameters.staticData.has3D = true; } @@ -722,12 +731,12 @@ std::vector<Feature> Renderer::Impl::querySourceFeatures(const std::string& sour return source->querySourceFeatures(options); } -void Renderer::Impl::onLowMemory() { +void Renderer::Impl::reduceMemoryUse() { assert(BackendScope::exists()); - backend.getContext().performCleanup(); for (const auto& entry : renderSources) { - entry.second->onLowMemory(); + entry.second->reduceMemoryUse(); } + backend.getContext().performCleanup(); observer->onInvalidate(); } diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp index 4f45d514a5..4675ac79ea 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -53,7 +53,7 @@ public: std::vector<Feature> querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const; std::vector<Feature> queryShapeAnnotations(const ScreenLineString&) const; - void onLowMemory(); + void reduceMemoryUse(); void dumDebugLogs(); private: diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp index df615a7e20..057ad5a4a7 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -76,8 +76,8 @@ std::vector<Feature> RenderCustomGeometrySource::querySourceFeatures(const Sourc return tilePyramid.querySourceFeatures(options); } -void RenderCustomGeometrySource::onLowMemory() { - tilePyramid.onLowMemory(); +void RenderCustomGeometrySource::reduceMemoryUse() { + tilePyramid.reduceMemoryUse(); } void RenderCustomGeometrySource::dumpDebugLogs() const { diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp index 82e691d5c9..033d731029 100644 --- a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp @@ -33,7 +33,7 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; - void onLowMemory() final; + void reduceMemoryUse() final; void dumpDebugLogs() const final; private: diff --git a/src/mbgl/renderer/sources/render_geojson_source.cpp b/src/mbgl/renderer/sources/render_geojson_source.cpp index 8ea80cd813..cbf4db70b5 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.cpp +++ b/src/mbgl/renderer/sources/render_geojson_source.cpp @@ -94,8 +94,8 @@ std::vector<Feature> RenderGeoJSONSource::querySourceFeatures(const SourceQueryO return tilePyramid.querySourceFeatures(options); } -void RenderGeoJSONSource::onLowMemory() { - tilePyramid.onLowMemory(); +void RenderGeoJSONSource::reduceMemoryUse() { + tilePyramid.reduceMemoryUse(); } void RenderGeoJSONSource::dumpDebugLogs() const { diff --git a/src/mbgl/renderer/sources/render_geojson_source.hpp b/src/mbgl/renderer/sources/render_geojson_source.hpp index 55166ea901..72fccbd043 100644 --- a/src/mbgl/renderer/sources/render_geojson_source.hpp +++ b/src/mbgl/renderer/sources/render_geojson_source.hpp @@ -37,7 +37,7 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; - void onLowMemory() final; + void reduceMemoryUse() final; void dumpDebugLogs() const final; private: diff --git a/src/mbgl/renderer/sources/render_image_source.hpp b/src/mbgl/renderer/sources/render_image_source.hpp index 72cf4cea61..85ee0ace11 100644 --- a/src/mbgl/renderer/sources/render_image_source.hpp +++ b/src/mbgl/renderer/sources/render_image_source.hpp @@ -37,7 +37,7 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; - void onLowMemory() final { + void reduceMemoryUse() final { } void dumpDebugLogs() const final; diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.cpp b/src/mbgl/renderer/sources/render_raster_dem_source.cpp index 76716518d7..b3153622c3 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.cpp @@ -32,21 +32,22 @@ void RenderRasterDEMSource::update(Immutable<style::Source::Impl> baseImpl_, enabled = needsRendering; - optional<Tileset> tileset = impl().getTileset(); - - if (!tileset) { - return; - } - - if (tileURLTemplates != tileset->tiles) { - tileURLTemplates = tileset->tiles; + optional<Tileset> _tileset = impl().getTileset(); + if (tileset != _tileset) { + tileset = _tileset; + maxzoom = tileset->zoomRange.max; // TODO: this removes existing buckets, and will cause flickering. // Should instead refresh tile data in place. tilePyramid.tiles.clear(); tilePyramid.renderTiles.clear(); tilePyramid.cache.clear(); } + // Allow clearing the tile pyramid first, before the early return in case + // the new tileset is not yet available or has an error in loading + if (!_tileset) { + return; + } tilePyramid.update(layers, needsRendering, @@ -154,8 +155,8 @@ std::vector<Feature> RenderRasterDEMSource::querySourceFeatures(const SourceQuer return {}; } -void RenderRasterDEMSource::onLowMemory() { - tilePyramid.onLowMemory(); +void RenderRasterDEMSource::reduceMemoryUse() { + tilePyramid.reduceMemoryUse(); } void RenderRasterDEMSource::dumpDebugLogs() const { diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.hpp b/src/mbgl/renderer/sources/render_raster_dem_source.hpp index b6b8bf977c..741214a14d 100644 --- a/src/mbgl/renderer/sources/render_raster_dem_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_dem_source.hpp @@ -33,14 +33,19 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; - void onLowMemory() final; + void reduceMemoryUse() final; void dumpDebugLogs() const final; + uint8_t getMaxZoom() const { + return maxzoom; + }; + private: const style::RasterSource::Impl& impl() const; TilePyramid tilePyramid; - optional<std::vector<std::string>> tileURLTemplates; + optional<Tileset> tileset; + uint8_t maxzoom = 15; protected: void onTileChanged(Tile&) final; diff --git a/src/mbgl/renderer/sources/render_raster_source.cpp b/src/mbgl/renderer/sources/render_raster_source.cpp index e99cd040e9..60b3fa9a3b 100644 --- a/src/mbgl/renderer/sources/render_raster_source.cpp +++ b/src/mbgl/renderer/sources/render_raster_source.cpp @@ -29,14 +29,10 @@ void RenderRasterSource::update(Immutable<style::Source::Impl> baseImpl_, enabled = needsRendering; - optional<Tileset> tileset = impl().getTileset(); + optional<Tileset> _tileset = impl().getTileset(); - if (!tileset) { - return; - } - - if (tileURLTemplates != tileset->tiles) { - tileURLTemplates = tileset->tiles; + if (tileset != _tileset) { + tileset = _tileset; // TODO: this removes existing buckets, and will cause flickering. // Should instead refresh tile data in place. @@ -44,6 +40,11 @@ void RenderRasterSource::update(Immutable<style::Source::Impl> baseImpl_, tilePyramid.renderTiles.clear(); tilePyramid.cache.clear(); } + // Allow clearing the tile pyramid first, before the early return in case + // the new tileset is not yet available or has an error in loading + if (!_tileset) { + return; + } tilePyramid.update(layers, needsRendering, @@ -84,8 +85,8 @@ std::vector<Feature> RenderRasterSource::querySourceFeatures(const SourceQueryOp return {}; } -void RenderRasterSource::onLowMemory() { - tilePyramid.onLowMemory(); +void RenderRasterSource::reduceMemoryUse() { + tilePyramid.reduceMemoryUse(); } void RenderRasterSource::dumpDebugLogs() const { diff --git a/src/mbgl/renderer/sources/render_raster_source.hpp b/src/mbgl/renderer/sources/render_raster_source.hpp index 25041fde43..78eda199ac 100644 --- a/src/mbgl/renderer/sources/render_raster_source.hpp +++ b/src/mbgl/renderer/sources/render_raster_source.hpp @@ -33,14 +33,14 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; - void onLowMemory() final; + void reduceMemoryUse() final; void dumpDebugLogs() const final; private: const style::RasterSource::Impl& impl() const; TilePyramid tilePyramid; - optional<std::vector<std::string>> tileURLTemplates; + optional<Tileset> tileset; }; template <> diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index d53023e4d0..e87bea5dcd 100644 --- a/src/mbgl/renderer/sources/render_vector_source.cpp +++ b/src/mbgl/renderer/sources/render_vector_source.cpp @@ -32,14 +32,10 @@ void RenderVectorSource::update(Immutable<style::Source::Impl> baseImpl_, enabled = needsRendering; - optional<Tileset> tileset = impl().getTileset(); + optional<Tileset> _tileset = impl().getTileset(); - if (!tileset) { - return; - } - - if (tileURLTemplates != tileset->tiles) { - tileURLTemplates = tileset->tiles; + if (tileset != _tileset) { + tileset = _tileset; // TODO: this removes existing buckets, and will cause flickering. // Should instead refresh tile data in place. @@ -47,6 +43,11 @@ void RenderVectorSource::update(Immutable<style::Source::Impl> baseImpl_, tilePyramid.renderTiles.clear(); tilePyramid.cache.clear(); } + // Allow clearing the tile pyramid first, before the early return in case + // the new tileset is not yet available or has an error in loading + if (!_tileset) { + return; + } tilePyramid.update(layers, needsRendering, @@ -87,8 +88,8 @@ std::vector<Feature> RenderVectorSource::querySourceFeatures(const SourceQueryOp return tilePyramid.querySourceFeatures(options); } -void RenderVectorSource::onLowMemory() { - tilePyramid.onLowMemory(); +void RenderVectorSource::reduceMemoryUse() { + tilePyramid.reduceMemoryUse(); } void RenderVectorSource::dumpDebugLogs() const { diff --git a/src/mbgl/renderer/sources/render_vector_source.hpp b/src/mbgl/renderer/sources/render_vector_source.hpp index 4a992e854f..592160dc16 100644 --- a/src/mbgl/renderer/sources/render_vector_source.hpp +++ b/src/mbgl/renderer/sources/render_vector_source.hpp @@ -33,14 +33,14 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const final; - void onLowMemory() final; + void reduceMemoryUse() final; void dumpDebugLogs() const final; private: const style::VectorSource::Impl& impl() const; TilePyramid tilePyramid; - optional<std::vector<std::string>> tileURLTemplates; + optional<Tileset> tileset; }; template <> diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 07239b7a1c..c4372e7112 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -303,7 +303,7 @@ void TilePyramid::setCacheSize(size_t size) { cache.setSize(size); } -void TilePyramid::onLowMemory() { +void TilePyramid::reduceMemoryUse() { cache.clear(); } diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp index ad3f91bf88..2638599c38 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -59,7 +59,7 @@ public: std::vector<Feature> querySourceFeatures(const SourceQueryOptions&) const; void setCacheSize(size_t); - void onLowMemory(); + void reduceMemoryUse(); void setObserver(TileObserver*); void dumpDebugLogs() const; diff --git a/src/mbgl/shaders/collision_circle.cpp b/src/mbgl/shaders/collision_circle.cpp index f220586245..82ebbf05a0 100644 --- a/src/mbgl/shaders/collision_circle.cpp +++ b/src/mbgl/shaders/collision_circle.cpp @@ -26,7 +26,10 @@ varying vec2 v_extrude_scale; void main() { vec4 projectedPoint = u_matrix * vec4(a_anchor_pos, 0, 1); highp float camera_to_anchor_distance = projectedPoint.w; - highp float collision_perspective_ratio = 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance); + highp float collision_perspective_ratio = clamp( + 0.5 + 0.5 * (u_camera_to_center_distance / camera_to_anchor_distance), + 0.0, // Prevents oversized near-field circles in pitched/overzoomed tiles + 4.0); gl_Position = u_matrix * vec4(a_pos, 0.0, 1.0); @@ -43,6 +46,7 @@ void main() { )MBGL_SHADER"; const char* collision_circle::fragmentSource = R"MBGL_SHADER( +uniform float u_overscale_factor; varying float v_placed; varying float v_notUsed; @@ -68,7 +72,7 @@ void main() { float extrude_scale_length = length(v_extrude_scale); float extrude_length = length(v_extrude) * extrude_scale_length; - float stroke_width = 15.0 * extrude_scale_length; + float stroke_width = 15.0 * extrude_scale_length / u_overscale_factor; float radius = v_radius * extrude_scale_length; float distance_to_edge = abs(extrude_length - radius); diff --git a/src/mbgl/shaders/heatmap.cpp b/src/mbgl/shaders/heatmap.cpp new file mode 100644 index 0000000000..19927cfcc6 --- /dev/null +++ b/src/mbgl/shaders/heatmap.cpp @@ -0,0 +1,128 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/heatmap.hpp> + +namespace mbgl { +namespace shaders { + +const char* heatmap::name = "heatmap"; +const char* heatmap::vertexSource = R"MBGL_SHADER( + +#ifndef HAS_UNIFORM_u_weight +uniform lowp float a_weight_t; +attribute highp vec2 a_weight; +varying highp float weight; +#else +uniform highp float u_weight; +#endif + + +#ifndef HAS_UNIFORM_u_radius +uniform lowp float a_radius_t; +attribute mediump vec2 a_radius; +#else +uniform mediump float u_radius; +#endif + + +uniform mat4 u_matrix; +uniform float u_extrude_scale; +uniform float u_opacity; +uniform float u_intensity; + +attribute vec2 a_pos; + +varying vec2 v_extrude; + +// Effective "0" in the kernel density texture to adjust the kernel size to; +// this empirically chosen number minimizes artifacts on overlapping kernels +// for typical heatmap cases (assuming clustered source) +const highp float ZERO = 1.0 / 255.0 / 16.0; + +// Gaussian kernel coefficient: 1 / sqrt(2 * PI) +#define GAUSS_COEF 0.3989422804014327 + +void main(void) { + +#ifndef HAS_UNIFORM_u_weight + weight = unpack_mix_vec2(a_weight, a_weight_t); +#else + highp float weight = u_weight; +#endif + + +#ifndef HAS_UNIFORM_u_radius + mediump float radius = unpack_mix_vec2(a_radius, a_radius_t); +#else + mediump float radius = u_radius; +#endif + + + // unencode the extrusion vector that we snuck into the a_pos vector + vec2 unscaled_extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0); + + // This 'extrude' comes in ranging from [-1, -1], to [1, 1]. We'll use + // it to produce the vertices of a square mesh framing the point feature + // we're adding to the kernel density texture. We'll also pass it as + // a varying, so that the fragment shader can determine the distance of + // each fragment from the point feature. + // Before we do so, we need to scale it up sufficiently so that the + // kernel falls effectively to zero at the edge of the mesh. + // That is, we want to know S such that + // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO + // Which solves to: + // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 + float S = sqrt(-2.0 * log(ZERO / weight / u_intensity / GAUSS_COEF)) / 3.0; + + // Pass the varying in units of radius + v_extrude = S * unscaled_extrude; + + // Scale by radius and the zoom-based scale factor to produce actual + // mesh position + vec2 extrude = v_extrude * radius * u_extrude_scale; + + // multiply a_pos by 0.5, since we had it * 2 in order to sneak + // in extrusion data + vec4 pos = vec4(floor(a_pos * 0.5) + extrude, 0, 1); + + gl_Position = u_matrix * pos; +} + +)MBGL_SHADER"; +const char* heatmap::fragmentSource = R"MBGL_SHADER( + +#ifndef HAS_UNIFORM_u_weight +varying highp float weight; +#else +uniform highp float u_weight; +#endif + + +uniform highp float u_intensity; +varying vec2 v_extrude; + +// Gaussian kernel coefficient: 1 / sqrt(2 * PI) +#define GAUSS_COEF 0.3989422804014327 + +void main() { + +#ifdef HAS_UNIFORM_u_weight + highp float weight = u_weight; +#endif + + + // Kernel density estimation with a Gaussian kernel of size 5x5 + float d = -0.5 * 3.0 * 3.0 * dot(v_extrude, v_extrude); + float val = weight * u_intensity * GAUSS_COEF * exp(d); + + gl_FragColor = vec4(val, 1.0, 1.0, 1.0); + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(1.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap.hpp b/src/mbgl/shaders/heatmap.hpp new file mode 100644 index 0000000000..a3c64db942 --- /dev/null +++ b/src/mbgl/shaders/heatmap.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class heatmap { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap_texture.cpp b/src/mbgl/shaders/heatmap_texture.cpp new file mode 100644 index 0000000000..c5d35c48ae --- /dev/null +++ b/src/mbgl/shaders/heatmap_texture.cpp @@ -0,0 +1,42 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include <mbgl/shaders/heatmap_texture.hpp> + +namespace mbgl { +namespace shaders { + +const char* heatmap_texture::name = "heatmap_texture"; +const char* heatmap_texture::vertexSource = R"MBGL_SHADER( +uniform mat4 u_matrix; +uniform vec2 u_world; +attribute vec2 a_pos; +varying vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos * u_world, 0, 1); + + v_pos.x = a_pos.x; + v_pos.y = 1.0 - a_pos.y; +} + +)MBGL_SHADER"; +const char* heatmap_texture::fragmentSource = R"MBGL_SHADER( +uniform sampler2D u_image; +uniform sampler2D u_color_ramp; +uniform float u_opacity; +varying vec2 v_pos; + +void main() { + float t = texture2D(u_image, v_pos).r; + vec4 color = texture2D(u_color_ramp, vec2(t, 0.5)); + gl_FragColor = color * u_opacity; + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(0.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap_texture.hpp b/src/mbgl/shaders/heatmap_texture.hpp new file mode 100644 index 0000000000..c51dc6b178 --- /dev/null +++ b/src/mbgl/shaders/heatmap_texture.hpp @@ -0,0 +1,16 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#pragma once + +namespace mbgl { +namespace shaders { + +class heatmap_texture { +public: + static const char* name; + static const char* vertexSource; + static const char* fragmentSource; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/hillshade_prepare.cpp b/src/mbgl/shaders/hillshade_prepare.cpp index 733658435e..8d0571f6a4 100644 --- a/src/mbgl/shaders/hillshade_prepare.cpp +++ b/src/mbgl/shaders/hillshade_prepare.cpp @@ -29,6 +29,7 @@ uniform sampler2D u_image; varying vec2 v_pos; uniform vec2 u_dimension; uniform float u_zoom; +uniform float u_maxzoom; float getElevation(vec2 coord, float bias) { // Convert encoded elevation value to meters @@ -71,16 +72,16 @@ void main() { // which can be reduced to: pow(2, 19.25619978527 - u_zoom) // we want to vertically exaggerate the hillshading though, because otherwise // it is barely noticeable at low zooms. to do this, we multiply this by some - // scale factor pow(2, (u_zoom - 14) * a) where a is an arbitrary value and 14 is the - // maxzoom of the tile source. here we use a=0.3 which works out to the - // expression below. see nickidlugash's awesome breakdown for more info + // scale factor pow(2, (u_zoom - u_maxzoom) * a) where a is an arbitrary value + // Here we use a=0.3 which works out to the expression below. see + // nickidlugash's awesome breakdown for more info // https://github.com/mapbox/mapbox-gl-js/pull/5286#discussion_r148419556 float exaggeration = u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3; vec2 deriv = vec2( (c + f + f + i) - (a + d + d + g), (g + h + h + i) - (a + b + b + c) - ) / pow(2.0, (u_zoom - 14.0) * exaggeration + 19.2562 - u_zoom); + ) / pow(2.0, (u_zoom - u_maxzoom) * exaggeration + 19.2562 - u_zoom); gl_FragColor = clamp(vec4( deriv.x / 2.0 + 0.5, diff --git a/src/mbgl/storage/asset_file_source.hpp b/src/mbgl/storage/asset_file_source.hpp index 6ed7af8aaf..5d98b4e69e 100644 --- a/src/mbgl/storage/asset_file_source.hpp +++ b/src/mbgl/storage/asset_file_source.hpp @@ -15,6 +15,8 @@ public: std::unique_ptr<AsyncRequest> request(const Resource&, Callback) override; + static bool acceptsURL(const std::string& url); + private: class Impl; diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp index ad6998341d..19472bc8d6 100644 --- a/src/mbgl/style/conversion/layer.cpp +++ b/src/mbgl/style/conversion/layer.cpp @@ -6,6 +6,7 @@ #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> @@ -165,6 +166,8 @@ optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(c converted = convertVectorLayer<SymbolLayer>(*id, value, error); } else if (*type == "raster") { converted = convertRasterLayer(*id, value, error); + } else if (*type == "heatmap") { + converted = convertVectorLayer<HeatmapLayer>(*id, value, error); } else if (*type == "hillshade") { converted = convertHillshadeLayer(*id, value, error); } else if (*type == "background") { diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp index adfcc4dd61..25c8fdb1ca 100644 --- a/src/mbgl/style/conversion/make_property_setters.hpp +++ b/src/mbgl/style/conversion/make_property_setters.hpp @@ -8,6 +8,7 @@ #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/layers/circle_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> #include <mbgl/style/layers/hillshade_layer.hpp> @@ -72,6 +73,7 @@ inline auto makeLayoutPropertySetters() { + return result; } @@ -166,6 +168,17 @@ inline auto makePaintPropertySetters() { result["circle-stroke-opacity"] = &setProperty<CircleLayer, DataDrivenPropertyValue<float>, &CircleLayer::setCircleStrokeOpacity>; result["circle-stroke-opacity-transition"] = &setTransition<CircleLayer, &CircleLayer::setCircleStrokeOpacityTransition>; + result["heatmap-radius"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapRadius>; + result["heatmap-radius-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapRadiusTransition>; + result["heatmap-weight"] = &setProperty<HeatmapLayer, DataDrivenPropertyValue<float>, &HeatmapLayer::setHeatmapWeight>; + result["heatmap-weight-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapWeightTransition>; + result["heatmap-intensity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapIntensity>; + result["heatmap-intensity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapIntensityTransition>; + result["heatmap-color"] = &setProperty<HeatmapLayer, HeatmapColorPropertyValue, &HeatmapLayer::setHeatmapColor>; + result["heatmap-color-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapColorTransition>; + result["heatmap-opacity"] = &setProperty<HeatmapLayer, PropertyValue<float>, &HeatmapLayer::setHeatmapOpacity>; + result["heatmap-opacity-transition"] = &setTransition<HeatmapLayer, &HeatmapLayer::setHeatmapOpacityTransition>; + result["fill-extrusion-opacity"] = &setProperty<FillExtrusionLayer, PropertyValue<float>, &FillExtrusionLayer::setFillExtrusionOpacity>; result["fill-extrusion-opacity-transition"] = &setTransition<FillExtrusionLayer, &FillExtrusionLayer::setFillExtrusionOpacityTransition>; result["fill-extrusion-color"] = &setProperty<FillExtrusionLayer, DataDrivenPropertyValue<Color>, &FillExtrusionLayer::setFillExtrusionColor>; diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp index 9e382b9c38..e3716a18dc 100644 --- a/src/mbgl/style/conversion/property_setter.hpp +++ b/src/mbgl/style/conversion/property_setter.hpp @@ -5,6 +5,7 @@ #include <mbgl/style/conversion/constant.hpp> #include <mbgl/style/conversion/property_value.hpp> #include <mbgl/style/conversion/data_driven_property_value.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> #include <mbgl/style/conversion/transition_options.hpp> #include <string> diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp index 6e559c0cac..6d89cef944 100644 --- a/src/mbgl/style/conversion/tileset.cpp +++ b/src/mbgl/style/conversion/tileset.cpp @@ -6,7 +6,7 @@ namespace style { namespace conversion { bool validateLatitude(const double lat) { - return lat < 90 && lat > -90; + return lat <= 90 && lat >= -90; } optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error& error) const { @@ -40,6 +40,16 @@ optional<Tileset> Converter<Tileset>::operator()(const Convertible& value, Error } } + auto encodingValue = objectMember(value, "encoding"); + if (encodingValue) { + optional<std::string> encoding = toString(*encodingValue); + if (encoding && *encoding == "terrarium") { + result.encoding = Tileset::DEMEncoding::Terrarium; + } else if (encoding && *encoding != "mapbox") { + error = { "invalid raster-dem encoding type - valid types are 'mapbox' and 'terrarium' " }; + } + } + auto minzoomValue = objectMember(value, "minzoom"); if (minzoomValue) { optional<float> minzoom = toNumber(*minzoomValue); diff --git a/src/mbgl/style/layers/heatmap_layer.cpp b/src/mbgl/style/layers/heatmap_layer.cpp new file mode 100644 index 0000000000..4989ff15f1 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer.cpp @@ -0,0 +1,239 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_impl.hpp> +#include <mbgl/style/layer_observer.hpp> +// for constructing default heatmap-color ramp expression from style JSON +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> + +namespace mbgl { +namespace style { + +HeatmapLayer::HeatmapLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable<Impl>(LayerType::Heatmap, layerID, sourceID)) { +} + +HeatmapLayer::HeatmapLayer(Immutable<Impl> impl_) + : Layer(std::move(impl_)) { +} + +HeatmapLayer::~HeatmapLayer() = default; + +const HeatmapLayer::Impl& HeatmapLayer::impl() const { + return static_cast<const Impl&>(*baseImpl); +} + +Mutable<HeatmapLayer::Impl> HeatmapLayer::mutableImpl() const { + return makeMutable<Impl>(impl()); +} + +std::unique_ptr<Layer> HeatmapLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = HeatmapPaintProperties::Transitionable(); + return std::make_unique<HeatmapLayer>(std::move(impl_)); +} + +void HeatmapLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const { +} + +// Source + +const std::string& HeatmapLayer::getSourceID() const { + return impl().source; +} + +void HeatmapLayer::setSourceLayer(const std::string& sourceLayer) { + auto impl_ = mutableImpl(); + impl_->sourceLayer = sourceLayer; + baseImpl = std::move(impl_); +} + +const std::string& HeatmapLayer::getSourceLayer() const { + return impl().sourceLayer; +} + +// Filter + +void HeatmapLayer::setFilter(const Filter& filter) { + auto impl_ = mutableImpl(); + impl_->filter = filter; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +const Filter& HeatmapLayer::getFilter() const { + return impl().filter; +} + +// Visibility + +void HeatmapLayer::setVisibility(VisibilityType value) { + if (value == getVisibility()) + return; + auto impl_ = mutableImpl(); + impl_->visibility = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Zoom range + +void HeatmapLayer::setMinZoom(float minZoom) { + auto impl_ = mutableImpl(); + impl_->minZoom = minZoom; + baseImpl = std::move(impl_); +} + +void HeatmapLayer::setMaxZoom(float maxZoom) { + auto impl_ = mutableImpl(); + impl_->maxZoom = maxZoom; + baseImpl = std::move(impl_); +} + +// Layout properties + + +// Paint properties + +DataDrivenPropertyValue<float> HeatmapLayer::getDefaultHeatmapRadius() { + return { 30 }; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapRadius() const { + return impl().paint.template get<HeatmapRadius>().value; +} + +void HeatmapLayer::setHeatmapRadius(DataDrivenPropertyValue<float> value) { + if (value == getHeatmapRadius()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapRadius>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapRadiusTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapRadius>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapRadiusTransition() const { + return impl().paint.template get<HeatmapRadius>().options; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getDefaultHeatmapWeight() { + return { 1 }; +} + +DataDrivenPropertyValue<float> HeatmapLayer::getHeatmapWeight() const { + return impl().paint.template get<HeatmapWeight>().value; +} + +void HeatmapLayer::setHeatmapWeight(DataDrivenPropertyValue<float> value) { + if (value == getHeatmapWeight()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapWeight>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapWeightTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapWeight>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapWeightTransition() const { + return impl().paint.template get<HeatmapWeight>().options; +} + +PropertyValue<float> HeatmapLayer::getDefaultHeatmapIntensity() { + return { 1 }; +} + +PropertyValue<float> HeatmapLayer::getHeatmapIntensity() const { + return impl().paint.template get<HeatmapIntensity>().value; +} + +void HeatmapLayer::setHeatmapIntensity(PropertyValue<float> value) { + if (value == getHeatmapIntensity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapIntensity>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapIntensityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapIntensity>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapIntensityTransition() const { + return impl().paint.template get<HeatmapIntensity>().options; +} + +HeatmapColorPropertyValue HeatmapLayer::getDefaultHeatmapColor() { + conversion::Error error; + std::string rawValue = R"JSON(["interpolate",["linear"],["heatmap-density"],0,"rgba(0, 0, 255, 0)",0.1,"royalblue",0.3,"cyan",0.5,"lime",0.7,"yellow",1,"red"])JSON"; + return *conversion::convertJSON<HeatmapColorPropertyValue>(rawValue, error); +} + +HeatmapColorPropertyValue HeatmapLayer::getHeatmapColor() const { + return impl().paint.template get<HeatmapColor>().value; +} + +void HeatmapLayer::setHeatmapColor(HeatmapColorPropertyValue value) { + if (value == getHeatmapColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapColor>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapColor>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapColorTransition() const { + return impl().paint.template get<HeatmapColor>().options; +} + +PropertyValue<float> HeatmapLayer::getDefaultHeatmapOpacity() { + return { 1 }; +} + +PropertyValue<float> HeatmapLayer::getHeatmapOpacity() const { + return impl().paint.template get<HeatmapOpacity>().value; +} + +void HeatmapLayer::setHeatmapOpacity(PropertyValue<float> value) { + if (value == getHeatmapOpacity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapOpacity>().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapOpacityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get<HeatmapOpacity>().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapOpacityTransition() const { + return impl().paint.template get<HeatmapOpacity>().options; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.cpp b/src/mbgl/style/layers/heatmap_layer_impl.cpp new file mode 100644 index 0000000000..af20888d9d --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.cpp @@ -0,0 +1,15 @@ +#include <mbgl/style/layers/heatmap_layer_impl.hpp> + +namespace mbgl { +namespace style { + +bool HeatmapLayer::Impl::hasLayoutDifference(const Layer::Impl& other) const { + assert(dynamic_cast<const HeatmapLayer::Impl*>(&other)); + const auto& impl = static_cast<const style::HeatmapLayer::Impl&>(other); + return filter != impl.filter || + visibility != impl.visibility || + paint.hasDataDrivenPropertyDifference(impl.paint); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.hpp b/src/mbgl/style/layers/heatmap_layer_impl.hpp new file mode 100644 index 0000000000..cc27c3076a --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <mbgl/style/layer_impl.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +namespace mbgl { +namespace style { + +class HeatmapLayer::Impl : public Layer::Impl { +public: + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override; + + HeatmapPaintProperties::Transitionable paint; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.cpp b/src/mbgl/style/layers/heatmap_layer_properties.cpp new file mode 100644 index 0000000000..2edb839589 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.cpp @@ -0,0 +1,9 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include <mbgl/style/layers/heatmap_layer_properties.hpp> + +namespace mbgl { +namespace style { + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.hpp b/src/mbgl/style/layers/heatmap_layer_properties.hpp new file mode 100644 index 0000000000..f7afa5fbeb --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.hpp @@ -0,0 +1,40 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include <mbgl/style/types.hpp> +#include <mbgl/style/layout_property.hpp> +#include <mbgl/style/paint_property.hpp> +#include <mbgl/style/properties.hpp> +#include <mbgl/programs/attributes.hpp> +#include <mbgl/programs/uniforms.hpp> + +namespace mbgl { +namespace style { + +struct HeatmapRadius : DataDrivenPaintProperty<float, attributes::a_radius, uniforms::u_radius> { + static float defaultValue() { return 30; } +}; + +struct HeatmapWeight : DataDrivenPaintProperty<float, attributes::a_weight, uniforms::u_weight> { + static float defaultValue() { return 1; } +}; + +struct HeatmapIntensity : PaintProperty<float> { + static float defaultValue() { return 1; } +}; + +struct HeatmapOpacity : PaintProperty<float> { + static float defaultValue() { return 1; } +}; + +class HeatmapPaintProperties : public Properties< + HeatmapRadius, + HeatmapWeight, + HeatmapIntensity, + HeatmapColor, + HeatmapOpacity +> {}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs index be44bb353d..657a7f5a8a 100644 --- a/src/mbgl/style/layers/layer.cpp.ejs +++ b/src/mbgl/style/layers/layer.cpp.ejs @@ -8,6 +8,12 @@ #include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer.hpp> #include <mbgl/style/layers/<%- type.replace('-', '_') %>_layer_impl.hpp> #include <mbgl/style/layer_observer.hpp> +<% if (type === 'heatmap') { -%> +// for constructing default heatmap-color ramp expression from style JSON +#include <mbgl/style/conversion.hpp> +#include <mbgl/style/conversion/json.hpp> +#include <mbgl/style/conversion/heatmap_color_property_value.hpp> +<% } -%> namespace mbgl { namespace style { @@ -134,7 +140,13 @@ void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>(<%- propertyV // Paint properties <% for (const property of paintProperties) { %> <%- propertyValueType(property) %> <%- camelize(type) %>Layer::getDefault<%- camelize(property.name) %>() { +<% if (property.name === 'heatmap-color') { -%> + conversion::Error error; + std::string rawValue = R"JSON(<%- JSON.stringify(property.default) %>)JSON"; + return *conversion::convertJSON<<%- propertyValueType(property)%>>(rawValue, error); +<% } else { -%> return { <%- defaultValue(property) %> }; +<% } -%> } <%- propertyValueType(property) %> <%- camelize(type) %>Layer::get<%- camelize(property.name) %>() const { diff --git a/src/mbgl/style/layers/layer_properties.hpp.ejs b/src/mbgl/style/layers/layer_properties.hpp.ejs index cde1b80b7b..1bceb84960 100644 --- a/src/mbgl/style/layers/layer_properties.hpp.ejs +++ b/src/mbgl/style/layers/layer_properties.hpp.ejs @@ -25,6 +25,7 @@ struct <%- camelize(property.name) %> : <%- layoutPropertyType(property, type) % <% } -%> <% for (const property of paintProperties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> struct <%- camelize(property.name) %> : <%- paintPropertyType(property, type) %> { static <%- evaluatedType(property) %> defaultValue() { return <%- defaultValue(property) %>; } }; diff --git a/src/mbgl/style/paint_property.hpp b/src/mbgl/style/paint_property.hpp index c4c996b3bd..195eb645a9 100644 --- a/src/mbgl/style/paint_property.hpp +++ b/src/mbgl/style/paint_property.hpp @@ -2,6 +2,7 @@ #include <mbgl/style/properties.hpp> #include <mbgl/style/property_value.hpp> +#include <mbgl/style/heatmap_color_property_value.hpp> #include <mbgl/style/data_driven_property_value.hpp> #include <mbgl/renderer/property_evaluator.hpp> #include <mbgl/renderer/cross_faded_property_evaluator.hpp> @@ -48,5 +49,27 @@ public: static constexpr bool IsDataDriven = false; }; +/* + * Special-case paint property traits for heatmap-color, needed because + * heatmap-color values do not fit into the + * Undefined | Value | {Camera,Source,Composite}Function taxonomy that applies + * to all other paint properties. + * + * These traits are provided here--despite the fact that heatmap-color + * is not used like other paint properties--to allow the parameter-pack-based + * batch evaluation of paint properties to compile properly. + */ +class HeatmapColor { +public: + using TransitionableType = Transitionable<HeatmapColorPropertyValue>; + using UnevaluatedType = Transitioning<HeatmapColorPropertyValue>; + using EvaluatorType = PropertyEvaluator<Color>; + using PossiblyEvaluatedType = Color; + using Type = Color; + static constexpr bool IsDataDriven = false; + + static Color defaultValue() { return {}; } +}; + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index f2a9c0f222..d330b3120a 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -6,6 +6,7 @@ #include <mbgl/style/layers/background_layer.hpp> #include <mbgl/style/layers/fill_layer.hpp> #include <mbgl/style/layers/fill_extrusion_layer.hpp> +#include <mbgl/style/layers/heatmap_layer.hpp> #include <mbgl/style/layers/line_layer.hpp> #include <mbgl/style/layers/circle_layer.hpp> #include <mbgl/style/layers/raster_layer.hpp> diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 400fe79ac5..fc24c78902 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -230,6 +230,8 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& if (bucket.hasCollisionBoxData()) bucket.collisionBox.dynamicVertices.clear(); if (bucket.hasCollisionCircleData()) bucket.collisionCircle.dynamicVertices.clear(); + JointOpacityState duplicateOpacityState(false, false, true); + JointOpacityState defaultOpacityState( bucket.layout.get<style::TextAllowOverlap>(), bucket.layout.get<style::IconAllowOverlap>(), @@ -239,9 +241,12 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set<uint32_t>& bool isDuplicate = seenCrossTileIDs.count(symbolInstance.crossTileID) > 0; auto it = opacities.find(symbolInstance.crossTileID); - auto opacityState = it != opacities.end() && !isDuplicate ? - it->second : - defaultOpacityState; + auto opacityState = defaultOpacityState; + if (isDuplicate) { + opacityState = duplicateOpacityState; + } else if (it != opacities.end()) { + opacityState = it->second; + } if (it == opacities.end()) { opacities.emplace(symbolInstance.crossTileID, defaultOpacityState); diff --git a/src/mbgl/tile/custom_geometry_tile.cpp b/src/mbgl/tile/custom_geometry_tile.cpp index 7c8a85c4a4..33962ad87d 100644 --- a/src/mbgl/tile/custom_geometry_tile.cpp +++ b/src/mbgl/tile/custom_geometry_tile.cpp @@ -39,7 +39,7 @@ void CustomGeometryTile::setTileData(const GeoJSON& geoJSON) { vtOptions.extent = util::EXTENT; vtOptions.buffer = ::round(scale * options.buffer); vtOptions.tolerance = scale * options.tolerance; - featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions).features; + featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions, options.wrap, options.clip).features; } else { setNecessity(TileNecessity::Optional); } diff --git a/src/mbgl/tile/raster_dem_tile.cpp b/src/mbgl/tile/raster_dem_tile.cpp index b270378ece..5db298cf4c 100644 --- a/src/mbgl/tile/raster_dem_tile.cpp +++ b/src/mbgl/tile/raster_dem_tile.cpp @@ -21,6 +21,7 @@ RasterDEMTile::RasterDEMTile(const OverscaledTileID& id_, worker(parameters.workerScheduler, ActorRef<RasterDEMTile>(*this, mailbox)) { + encoding = tileset.encoding; if ( id.canonical.y == 0 ){ // this tile doesn't have upper neighboring tiles so marked those as backfilled neighboringTiles = neighboringTiles | DEMTileNeighbors::NoUpper; @@ -47,7 +48,7 @@ void RasterDEMTile::setMetadata(optional<Timestamp> modified_, optional<Timestam void RasterDEMTile::setData(std::shared_ptr<const std::string> data) { pending = true; ++correlationID; - worker.invoke(&RasterDEMTileWorker::parse, data, correlationID); + worker.invoke(&RasterDEMTileWorker::parse, data, correlationID, encoding); } void RasterDEMTile::onParsed(std::unique_ptr<HillshadeBucket> result, const uint64_t resultCorrelationID) { diff --git a/src/mbgl/tile/raster_dem_tile.hpp b/src/mbgl/tile/raster_dem_tile.hpp index 68f8a91e00..0c8dd75961 100644 --- a/src/mbgl/tile/raster_dem_tile.hpp +++ b/src/mbgl/tile/raster_dem_tile.hpp @@ -94,6 +94,7 @@ private: Actor<RasterDEMTileWorker> worker; uint64_t correlationID = 0; + Tileset::DEMEncoding encoding; // Contains the Bucket object for the tile. Buckets are render // objects and they get added by tile parsing operations. diff --git a/src/mbgl/tile/raster_dem_tile_worker.cpp b/src/mbgl/tile/raster_dem_tile_worker.cpp index ed8573788f..7338e578c7 100644 --- a/src/mbgl/tile/raster_dem_tile_worker.cpp +++ b/src/mbgl/tile/raster_dem_tile_worker.cpp @@ -10,14 +10,14 @@ RasterDEMTileWorker::RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef : parent(std::move(parent_)) { } -void RasterDEMTileWorker::parse(std::shared_ptr<const std::string> data, uint64_t correlationID) { +void RasterDEMTileWorker::parse(std::shared_ptr<const std::string> data, uint64_t correlationID, Tileset::DEMEncoding encoding) { if (!data) { parent.invoke(&RasterDEMTile::onParsed, nullptr, correlationID); // No data; empty tile. return; } try { - auto bucket = std::make_unique<HillshadeBucket>(decodeImage(*data)); + auto bucket = std::make_unique<HillshadeBucket>(decodeImage(*data), encoding); parent.invoke(&RasterDEMTile::onParsed, std::move(bucket), correlationID); } catch (...) { parent.invoke(&RasterDEMTile::onError, std::current_exception(), correlationID); diff --git a/src/mbgl/tile/raster_dem_tile_worker.hpp b/src/mbgl/tile/raster_dem_tile_worker.hpp index 14fd1f43b5..5a8222bc2d 100644 --- a/src/mbgl/tile/raster_dem_tile_worker.hpp +++ b/src/mbgl/tile/raster_dem_tile_worker.hpp @@ -1,6 +1,7 @@ #pragma once #include <mbgl/actor/actor_ref.hpp> +#include <mbgl/util/tileset.hpp> #include <memory> #include <string> @@ -13,7 +14,7 @@ class RasterDEMTileWorker { public: RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef<RasterDEMTile>); - void parse(std::shared_ptr<const std::string> data, uint64_t correlationID); + void parse(std::shared_ptr<const std::string> data, uint64_t correlationID, Tileset::DEMEncoding encoding); private: ActorRef<RasterDEMTile> parent; diff --git a/src/mbgl/util/offscreen_texture.cpp b/src/mbgl/util/offscreen_texture.cpp index 339e74b250..03f555eae0 100644 --- a/src/mbgl/util/offscreen_texture.cpp +++ b/src/mbgl/util/offscreen_texture.cpp @@ -11,20 +11,21 @@ OffscreenTexture& OffscreenTexture::operator=(OffscreenTexture&&) = default; class OffscreenTexture::Impl { public: - Impl(gl::Context& context_, const Size size_) - : context(context_), size(std::move(size_)) { + Impl(gl::Context& context_, const Size size_, const gl::TextureType type_) + : context(context_), size(std::move(size_)), type(type_) { assert(!size.isEmpty()); } Impl(gl::Context& context_, const Size size_, - gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& depth_) - : context(context_), size(std::move(size_)), depth(&depth_) { + gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& depth_, + const gl::TextureType type_) + : context(context_), size(std::move(size_)), depth(&depth_), type(type_) { assert(!size.isEmpty()); } void bind() { if (!framebuffer) { - texture = context.createTexture(size, gl::TextureFormat::RGBA); + texture = context.createTexture(size, gl::TextureFormat::RGBA, 0, type); if (depth) { framebuffer = context.createFramebuffer(*texture, *depth); } else { @@ -58,18 +59,21 @@ private: optional<gl::Framebuffer> framebuffer; optional<gl::Texture> texture; gl::Renderbuffer<gl::RenderbufferType::DepthComponent>* depth = nullptr; + const gl::TextureType type; }; OffscreenTexture::OffscreenTexture(gl::Context& context, - const Size size) - : impl(std::make_unique<Impl>(context, std::move(size))) { + const Size size, + const gl::TextureType type) + : impl(std::make_unique<Impl>(context, std::move(size), type)) { assert(!size.isEmpty()); } OffscreenTexture::OffscreenTexture(gl::Context& context, const Size size, - gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& renderbuffer) - : impl(std::make_unique<Impl>(context, std::move(size), renderbuffer)) { + gl::Renderbuffer<gl::RenderbufferType::DepthComponent>& renderbuffer, + const gl::TextureType type) + : impl(std::make_unique<Impl>(context, std::move(size), renderbuffer, type)) { assert(!size.isEmpty()); } diff --git a/src/mbgl/util/offscreen_texture.hpp b/src/mbgl/util/offscreen_texture.hpp index 7f7e0f0338..36f24f16d3 100644 --- a/src/mbgl/util/offscreen_texture.hpp +++ b/src/mbgl/util/offscreen_texture.hpp @@ -12,10 +12,12 @@ class Texture; class OffscreenTexture { public: OffscreenTexture(gl::Context&, - Size size = { 256, 256 }); + Size size = { 256, 256 }, + gl::TextureType type = gl::TextureType::UnsignedByte); OffscreenTexture(gl::Context&, Size size, - gl::Renderbuffer<gl::RenderbufferType::DepthComponent>&); + gl::Renderbuffer<gl::RenderbufferType::DepthComponent>&, + gl::TextureType type = gl::TextureType::UnsignedByte); ~OffscreenTexture(); OffscreenTexture(OffscreenTexture&&); OffscreenTexture& operator=(OffscreenTexture&&); diff --git a/test/geometry/dem_data.test.cpp b/test/geometry/dem_data.test.cpp index 30091973b2..3848f028f1 100644 --- a/test/geometry/dem_data.test.cpp +++ b/test/geometry/dem_data.test.cpp @@ -1,6 +1,7 @@ #include <mbgl/test/util.hpp> #include <mbgl/util/image.hpp> +#include <mbgl/util/tileset.hpp> #include <mbgl/geometry/dem_data.hpp> using namespace mbgl; @@ -14,30 +15,38 @@ auto fakeImage = [](Size s) { return img; }; -TEST(DEMData, Constructor) { +TEST(DEMData, ConstructorMapbox) { PremultipliedImage image = fakeImage({16, 16}); - DEMData pyramid(image); - - EXPECT_EQ(pyramid.dim, 16); - EXPECT_EQ(pyramid.border, 8); - EXPECT_EQ(pyramid.stride, 32); - EXPECT_EQ(pyramid.getImage()->bytes(), size_t(32*32*4)); - EXPECT_EQ(pyramid.dim, 16); - EXPECT_EQ(pyramid.border, 8); + DEMData demdata(image, Tileset::DEMEncoding::Mapbox); + + EXPECT_EQ(demdata.dim, 16); + EXPECT_EQ(demdata.border, 8); + EXPECT_EQ(demdata.stride, 32); + EXPECT_EQ(demdata.getImage()->bytes(), size_t(32*32*4)); +}; + +TEST(DEMData, ConstructorTerrarium) { + PremultipliedImage image = fakeImage({16, 16}); + DEMData demdata(image, Tileset::DEMEncoding::Terrarium); + + EXPECT_EQ(demdata.dim, 16); + EXPECT_EQ(demdata.border, 8); + EXPECT_EQ(demdata.stride, 32); + EXPECT_EQ(demdata.getImage()->bytes(), size_t(32*32*4)); }; TEST(DEMData, RoundTrip) { PremultipliedImage image = fakeImage({16, 16}); - DEMData pyramid(image); + DEMData demdata(image, Tileset::DEMEncoding::Mapbox); - pyramid.set(4, 6, 255); - EXPECT_EQ(pyramid.get(4, 6), 255); + demdata.set(4, 6, 255); + EXPECT_EQ(demdata.get(4, 6), 255); } TEST(DEMData, InitialBackfill) { PremultipliedImage image1 = fakeImage({4, 4}); - DEMData dem1(image1); + DEMData dem1(image1, Tileset::DEMEncoding::Mapbox); bool nonempty = true; // checking that a 1 px border around the fake image has been populated @@ -91,10 +100,10 @@ TEST(DEMData, InitialBackfill) { TEST(DEMData, BackfillNeighbor) { PremultipliedImage image1 = fakeImage({4, 4}); - DEMData dem0(image1); + DEMData dem0(image1, Tileset::DEMEncoding::Mapbox); PremultipliedImage image2 = fakeImage({4, 4}); - DEMData dem1(image2); + DEMData dem1(image2, Tileset::DEMEncoding::Mapbox); dem0.backfillBorder(dem1, -1, 0); for (int y = 0; y < 4; y++) { diff --git a/test/storage/asset_file_source.test.cpp b/test/storage/asset_file_source.test.cpp index a39d2963d2..978a41a306 100644 --- a/test/storage/asset_file_source.test.cpp +++ b/test/storage/asset_file_source.test.cpp @@ -69,6 +69,15 @@ TEST(AssetFileSource, Load) { loop.run(); } +TEST(AssetFileSource, AcceptsURL) { + EXPECT_TRUE(AssetFileSource::acceptsURL("asset://empty")); + EXPECT_TRUE(AssetFileSource::acceptsURL("asset:///test")); + EXPECT_FALSE(AssetFileSource::acceptsURL("assds://foo")); + EXPECT_FALSE(AssetFileSource::acceptsURL("asset:")); + EXPECT_FALSE(AssetFileSource::acceptsURL("style.json")); + EXPECT_FALSE(AssetFileSource::acceptsURL("")); +} + TEST(AssetFileSource, EmptyFile) { util::RunLoop loop; @@ -118,6 +127,23 @@ TEST(AssetFileSource, NonExistentFile) { loop.run(); } +TEST(AssetFileSource, InvalidURL) { + util::RunLoop loop; + + AssetFileSource fs("test/fixtures/storage/assets"); + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, "test://wrong-scheme" }, [&](Response res) { + req.reset(); + ASSERT_NE(nullptr, res.error); + EXPECT_EQ(Response::Error::Reason::Other, res.error->reason); + EXPECT_EQ("Invalid asset URL", res.error->message); + ASSERT_FALSE(res.data.get()); + loop.stop(); + }); + + loop.run(); +} + TEST(AssetFileSource, ReadDirectory) { util::RunLoop loop; diff --git a/test/storage/local_file_source.test.cpp b/test/storage/local_file_source.test.cpp index 4d509e6c7d..e1756f8e7d 100644 --- a/test/storage/local_file_source.test.cpp +++ b/test/storage/local_file_source.test.cpp @@ -20,6 +20,15 @@ std::string toAbsoluteURL(const std::string& fileName) { using namespace mbgl; +TEST(LocalFileSource, AcceptsURL) { + EXPECT_TRUE(LocalFileSource::acceptsURL("file://empty")); + EXPECT_TRUE(LocalFileSource::acceptsURL("file:///test")); + EXPECT_FALSE(LocalFileSource::acceptsURL("flie://foo")); + EXPECT_FALSE(LocalFileSource::acceptsURL("file:")); + EXPECT_FALSE(LocalFileSource::acceptsURL("style.json")); + EXPECT_FALSE(LocalFileSource::acceptsURL("")); +} + TEST(LocalFileSource, EmptyFile) { util::RunLoop loop; @@ -69,6 +78,23 @@ TEST(LocalFileSource, NonExistentFile) { loop.run(); } +TEST(LocalFileSource, InvalidURL) { + util::RunLoop loop; + + LocalFileSource fs; + + std::unique_ptr<AsyncRequest> req = fs.request({ Resource::Unknown, "test://wrong-scheme" }, [&](Response res) { + req.reset(); + ASSERT_NE(nullptr, res.error); + EXPECT_EQ(Response::Error::Reason::Other, res.error->reason); + EXPECT_EQ("Invalid file URL", res.error->message); + ASSERT_FALSE(res.data.get()); + loop.stop(); + }); + + loop.run(); +} + TEST(LocalFileSource, ReadDirectory) { util::RunLoop loop; diff --git a/test/style/conversion/tileset.test.cpp b/test/style/conversion/tileset.test.cpp index 8002cd038f..9487277cca 100644 --- a/test/style/conversion/tileset.test.cpp +++ b/test/style/conversion/tileset.test.cpp @@ -52,6 +52,16 @@ TEST(Tileset, InvalidBounds) { } } +TEST(Tileset, ValidWorldBounds) { + Error error; + mbgl::optional<Tileset> converted = convertJSON<Tileset>(R"JSON({ + "tiles": ["http://mytiles"], + "bounds": [-180, -90, 180, 90] + })JSON", error); + EXPECT_TRUE((bool) converted); + EXPECT_EQ(converted->bounds, LatLngBounds::hull({90, -180}, {-90, 180})); +} + TEST(Tileset, FullConversion) { Error error; Tileset converted = *convertJSON<Tileset>(R"JSON({ diff --git a/test/tile/raster_dem_tile.test.cpp b/test/tile/raster_dem_tile.test.cpp index c6f9b80b42..5a6f5a8c9a 100644 --- a/test/tile/raster_dem_tile.test.cpp +++ b/test/tile/raster_dem_tile.test.cpp @@ -62,7 +62,7 @@ TEST(RasterDEMTile, onError) { TEST(RasterDEMTile, onParsed) { RasterDEMTileTest test; RasterDEMTile tile(OverscaledTileID(0, 0, 0), test.tileParameters, test.tileset); - tile.onParsed(std::make_unique<HillshadeBucket>(PremultipliedImage({16, 16})), 0); + tile.onParsed(std::make_unique<HillshadeBucket>(PremultipliedImage({16, 16}), Tileset::DEMEncoding::Mapbox), 0); EXPECT_TRUE(tile.isRenderable()); EXPECT_TRUE(tile.isLoaded()); EXPECT_TRUE(tile.isComplete()); |