diff --git a/INSTALL.md b/INSTALL.md index fb619770a..a28348d15 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -7,50 +7,38 @@ that you can download instantly and get started with fast](https://www.mapbox.co Still with us? These are the instructions you'll need to build Mapbox GL Native from source on a variety of platforms and set up a development environment. -Your journey will start with getting the source code, then installing the -dependencies, and then setting up a development environment, which varies -depending on your operating system and what platform you want to develop for. +Your journey will start with installing dependencies, then getting the source code, and +then setting up a development environment, which varies depending on your +operating system and what platform you want to develop for. -## 1: Getting the source +## 1: Installing dependencies -Clone the git repository: +### macOS - git clone https://github.com/mapbox/mapbox-gl-native.git - cd mapbox-gl-native + 1. Install [Xcode](https://developer.apple.com/xcode/) + 2. Launch Xcode and install any updates + 3. Install [Homebrew](http://brew.sh) + 4. Install [Node.js](https://nodejs.org/), [CMake](https://cmake.org/), and [ccache](https://ccache.samba.org) with `brew install nodejs cmake ccache` + 5. Install [xcpretty](https://github.com/supermarin/xcpretty) with `[sudo] gem install xcpretty` (optional, used for prettifying command line builds) -## 2: Installing dependencies +### Linux -These dependencies are required for all operating systems and all platform -targets. - - - Modern C++ compiler that supports `-std=c++14`\* - - clang++ 3.5 or later _or_ - - g++-4.9 or later - - [CMake](https://cmake.org/) 3.1 or later (for build only) - - [cURL](https://curl.haxx.se) (for build only) - - [Node.js](https://nodejs.org/) 4.2.1 or later (for build only) - - [`pkg-config`](https://wiki.freedesktop.org/www/Software/pkg-config/) (for build only) - -**Note**: We partially support C++14 because GCC 4.9 does not fully implement the -final draft of the C++14 standard. More information in [DEVELOPING.md](DEVELOPING.md). - -Depending on your operating system and target, you'll need additional -dependencies: - -### Additional dependencies for Linux +Install the following: + - `clang++` 3.5 or later or `g++` 4.9 or later + - [git](https://git-scm.com/) + - [CMake](https://cmake.org/) 3.1 or later + - [cURL](https://curl.haxx.se) + - [Node.js](https://nodejs.org/) 4.2.1 or later - [`libcurl`](http://curl.haxx.se/libcurl/) (depends on OpenSSL) + - [ccache](https://ccache.samba.org) (optional, improves recompilation performance) -### Additional dependencies for macOS +## 2: Getting the source - - Apple Command Line Tools (available at [Apple Developer](https://developer.apple.com/download/more/)) - - [Homebrew](http://brew.sh) - - [Cask](http://caskroom.io/) (if building for Android) - - [xcpretty](https://github.com/supermarin/xcpretty) (`gem install xcpretty`) + Clone the git repository: -### Optional dependencies - -- [ccache](https://ccache.samba.org) (for build only; improves recompilation performance) + git clone https://github.com/mapbox/mapbox-gl-native.git + cd mapbox-gl-native ## 3: Setting up a development environment & building @@ -58,7 +46,7 @@ See the relevant SDK documentation for next steps: * [Maps SDK for Android](platform/android/) * [Maps SDK for iOS](platform/ios/) -* [Maps SDK for iOS](platform/macos/) +* [Maps SDK for macOS](platform/macos/) * [Mapbox Qt SDK](platform/qt/) * [Mapbox GL Native on Linux](platform/linux/) * [node-mapbox-gl-native](platform/node/) diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 24d526279..f24482e30 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -146,6 +146,10 @@ set(MBGL_CORE_FILES src/mbgl/programs/fill_extrusion_program.hpp src/mbgl/programs/fill_program.cpp src/mbgl/programs/fill_program.hpp + src/mbgl/programs/heatmap_program.cpp + src/mbgl/programs/heatmap_program.hpp + src/mbgl/programs/heatmap_texture_program.cpp + src/mbgl/programs/heatmap_texture_program.hpp src/mbgl/programs/hillshade_prepare_program.cpp src/mbgl/programs/hillshade_prepare_program.hpp src/mbgl/programs/hillshade_program.cpp @@ -170,6 +174,7 @@ set(MBGL_CORE_FILES include/mbgl/renderer/renderer.hpp include/mbgl/renderer/renderer_backend.hpp include/mbgl/renderer/renderer_frontend.hpp + include/mbgl/renderer/renderer_observer.hpp src/mbgl/renderer/backend_scope.cpp src/mbgl/renderer/bucket.hpp src/mbgl/renderer/bucket_parameters.cpp @@ -206,7 +211,6 @@ set(MBGL_CORE_FILES src/mbgl/renderer/renderer_backend.cpp src/mbgl/renderer/renderer_impl.cpp src/mbgl/renderer/renderer_impl.hpp - src/mbgl/renderer/renderer_observer.hpp src/mbgl/renderer/style_diff.cpp src/mbgl/renderer/style_diff.hpp src/mbgl/renderer/tile_mask.hpp @@ -225,6 +229,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/buckets/fill_bucket.hpp src/mbgl/renderer/buckets/fill_extrusion_bucket.cpp src/mbgl/renderer/buckets/fill_extrusion_bucket.hpp + src/mbgl/renderer/buckets/heatmap_bucket.cpp + src/mbgl/renderer/buckets/heatmap_bucket.hpp src/mbgl/renderer/buckets/hillshade_bucket.cpp src/mbgl/renderer/buckets/hillshade_bucket.hpp src/mbgl/renderer/buckets/line_bucket.cpp @@ -245,6 +251,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/layers/render_fill_extrusion_layer.hpp src/mbgl/renderer/layers/render_fill_layer.cpp src/mbgl/renderer/layers/render_fill_layer.hpp + src/mbgl/renderer/layers/render_heatmap_layer.cpp + src/mbgl/renderer/layers/render_heatmap_layer.hpp src/mbgl/renderer/layers/render_hillshade_layer.cpp src/mbgl/renderer/layers/render_hillshade_layer.hpp src/mbgl/renderer/layers/render_line_layer.cpp @@ -297,6 +305,10 @@ set(MBGL_CORE_FILES src/mbgl/shaders/fill_outline_pattern.hpp src/mbgl/shaders/fill_pattern.cpp src/mbgl/shaders/fill_pattern.hpp + src/mbgl/shaders/heatmap.cpp + src/mbgl/shaders/heatmap.hpp + src/mbgl/shaders/heatmap_texture.cpp + src/mbgl/shaders/heatmap_texture.hpp src/mbgl/shaders/hillshade.cpp src/mbgl/shaders/hillshade.hpp src/mbgl/shaders/hillshade_prepare.cpp @@ -349,6 +361,7 @@ set(MBGL_CORE_FILES include/mbgl/style/data_driven_property_value.hpp include/mbgl/style/filter.hpp include/mbgl/style/filter_evaluator.hpp + include/mbgl/style/heatmap_color_property_value.hpp include/mbgl/style/image.hpp include/mbgl/style/layer.hpp include/mbgl/style/layer_type.hpp @@ -401,6 +414,7 @@ set(MBGL_CORE_FILES include/mbgl/style/conversion/geojson.hpp include/mbgl/style/conversion/geojson_options.hpp include/mbgl/style/conversion/get_json_type.hpp + include/mbgl/style/conversion/heatmap_color_property_value.hpp include/mbgl/style/conversion/layer.hpp include/mbgl/style/conversion/light.hpp include/mbgl/style/conversion/position.hpp @@ -495,6 +509,7 @@ set(MBGL_CORE_FILES include/mbgl/style/layers/custom_layer.hpp include/mbgl/style/layers/fill_extrusion_layer.hpp include/mbgl/style/layers/fill_layer.hpp + include/mbgl/style/layers/heatmap_layer.hpp include/mbgl/style/layers/hillshade_layer.hpp include/mbgl/style/layers/line_layer.hpp include/mbgl/style/layers/raster_layer.hpp @@ -522,6 +537,11 @@ set(MBGL_CORE_FILES src/mbgl/style/layers/fill_layer_impl.hpp src/mbgl/style/layers/fill_layer_properties.cpp src/mbgl/style/layers/fill_layer_properties.hpp + src/mbgl/style/layers/heatmap_layer.cpp + src/mbgl/style/layers/heatmap_layer_impl.cpp + src/mbgl/style/layers/heatmap_layer_impl.hpp + src/mbgl/style/layers/heatmap_layer_properties.cpp + src/mbgl/style/layers/heatmap_layer_properties.hpp src/mbgl/style/layers/hillshade_layer.cpp src/mbgl/style/layers/hillshade_layer_impl.cpp src/mbgl/style/layers/hillshade_layer_impl.hpp diff --git a/include/mbgl/renderer/renderer.hpp b/include/mbgl/renderer/renderer.hpp index db28ee92f..798928087 100644 --- a/include/mbgl/renderer/renderer.hpp +++ b/include/mbgl/renderer/renderer.hpp @@ -48,7 +48,7 @@ public: void dumpDebugLogs(); // Memory - void onLowMemory(); + void reduceMemoryUse(); private: class Impl; diff --git a/src/mbgl/renderer/renderer_observer.hpp b/include/mbgl/renderer/renderer_observer.hpp similarity index 100% rename from src/mbgl/renderer/renderer_observer.hpp rename to include/mbgl/renderer/renderer_observer.hpp diff --git a/include/mbgl/style/conversion/custom_geometry_source_options.hpp b/include/mbgl/style/conversion/custom_geometry_source_options.hpp index 73b141e79..dedecd1aa 100644 --- a/include/mbgl/style/conversion/custom_geometry_source_options.hpp +++ b/include/mbgl/style/conversion/custom_geometry_source_options.hpp @@ -54,6 +54,26 @@ struct Converter { } } + const auto wrapValue = objectMember(value, "wrap"); + if (wrapValue) { + if (toBool(*wrapValue)) { + options.tileOptions.wrap = static_cast(*toBool(*wrapValue)); + } else { + error = { "CustomGeometrySource TileOptions wrap value must be a boolean" }; + return {}; + } + } + + const auto clipValue = objectMember(value, "clip"); + if (clipValue) { + if (toBool(*clipValue)) { + options.tileOptions.clip = static_cast(*toBool(*clipValue)); + } else { + error = { "CustomGeometrySource TileOptiosn clip value must be a boolean" }; + return {}; + } + } + return { options }; } diff --git a/include/mbgl/style/conversion/heatmap_color_property_value.hpp b/include/mbgl/style/conversion/heatmap_color_property_value.hpp new file mode 100644 index 000000000..e3689c524 --- /dev/null +++ b/include/mbgl/style/conversion/heatmap_color_property_value.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter { + optional operator()(const Convertible& value, Error& error) const { + if (isUndefined(value)) { + return HeatmapColorPropertyValue(); + } else if (isExpression(value)) { + optional> expression = convert>(value, error, expression::type::Color); + if (!expression) { + return {}; + } + assert(*expression); + if (!isFeatureConstant(**expression)) { + error = { "property expressions not supported" }; + return {}; + } + if (!isZoomConstant(**expression)) { + error = { "zoom expressions not supported" }; + return {}; + } + return {HeatmapColorPropertyValue(std::move(*expression))}; + } else { + error = { "heatmap-color must be an expression" }; + return {}; + } + } +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/heatmap_color_property_value.hpp b/include/mbgl/style/heatmap_color_property_value.hpp new file mode 100644 index 000000000..130639c6e --- /dev/null +++ b/include/mbgl/style/heatmap_color_property_value.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace style { + +/* + * Special-case implementation of (a subset of) the PropertyValue interface + * used for building the HeatmapColor paint property traits class. + */ +class HeatmapColorPropertyValue { +private: + std::shared_ptr value; + + friend bool operator==(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return (lhs.isUndefined() && rhs.isUndefined()) || (lhs.value && rhs.value && *(lhs.value) == *(rhs.value)); + } + + friend bool operator!=(const HeatmapColorPropertyValue& lhs, const HeatmapColorPropertyValue& rhs) { + return !(lhs == rhs); + } + +public: + HeatmapColorPropertyValue() : value(nullptr) {} + HeatmapColorPropertyValue(std::shared_ptr value_) : value(std::move(value_)) {} + + bool isUndefined() const { return value.get() == nullptr; } + + // noop, needed for batch evaluation of paint property values to compile + template + Color evaluate(const Evaluator&, TimePoint = {}) const { return {}; } + + Color evaluate(double heatmapDensity) const { + const auto result = value->evaluate(expression::EvaluationContext({}, nullptr, {heatmapDensity})); + return *expression::fromExpressionValue(*result); + } + + bool isDataDriven() const { return false; } + bool hasDataDrivenPropertyDifference(const HeatmapColorPropertyValue&) const { return false; } + + const expression::Expression& getExpression() const { return *value; } +}; + + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp index 8a5a4236b..12494f538 100644 --- a/include/mbgl/style/layer.hpp +++ b/include/mbgl/style/layer.hpp @@ -23,6 +23,7 @@ class HillshadeLayer; class BackgroundLayer; class CustomLayer; class FillExtrusionLayer; +class HeatmapLayer; class LayerObserver; /** @@ -93,6 +94,8 @@ public: return std::forward(visitor)(*as()); case LayerType::FillExtrusion: return std::forward(visitor)(*as()); + case LayerType::Heatmap: + return std::forward(visitor)(*as()); } diff --git a/include/mbgl/style/layer_type.hpp b/include/mbgl/style/layer_type.hpp index 951757134..0987ea4d0 100644 --- a/include/mbgl/style/layer_type.hpp +++ b/include/mbgl/style/layer_type.hpp @@ -13,6 +13,7 @@ enum class LayerType { Background, Custom, FillExtrusion, + Heatmap, }; } // namespace style diff --git a/include/mbgl/style/layers/heatmap_layer.hpp b/include/mbgl/style/layers/heatmap_layer.hpp new file mode 100644 index 000000000..33d927ad3 --- /dev/null +++ b/include/mbgl/style/layers/heatmap_layer.hpp @@ -0,0 +1,86 @@ +// This file is generated. Do not edit. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace style { + +class TransitionOptions; + +class HeatmapLayer : public Layer { +public: + HeatmapLayer(const std::string& layerID, const std::string& sourceID); + ~HeatmapLayer() final; + + // Source + const std::string& getSourceID() const; + const std::string& getSourceLayer() const; + void setSourceLayer(const std::string& sourceLayer); + + void setFilter(const Filter&); + const Filter& getFilter() const; + + // Visibility + void setVisibility(VisibilityType) final; + + // Zoom range + void setMinZoom(float) final; + void setMaxZoom(float) final; + + // Paint properties + + static DataDrivenPropertyValue getDefaultHeatmapRadius(); + DataDrivenPropertyValue getHeatmapRadius() const; + void setHeatmapRadius(DataDrivenPropertyValue); + void setHeatmapRadiusTransition(const TransitionOptions&); + TransitionOptions getHeatmapRadiusTransition() const; + + static DataDrivenPropertyValue getDefaultHeatmapWeight(); + DataDrivenPropertyValue getHeatmapWeight() const; + void setHeatmapWeight(DataDrivenPropertyValue); + void setHeatmapWeightTransition(const TransitionOptions&); + TransitionOptions getHeatmapWeightTransition() const; + + static PropertyValue getDefaultHeatmapIntensity(); + PropertyValue getHeatmapIntensity() const; + void setHeatmapIntensity(PropertyValue); + void setHeatmapIntensityTransition(const TransitionOptions&); + TransitionOptions getHeatmapIntensityTransition() const; + + static HeatmapColorPropertyValue getDefaultHeatmapColor(); + HeatmapColorPropertyValue getHeatmapColor() const; + void setHeatmapColor(HeatmapColorPropertyValue); + void setHeatmapColorTransition(const TransitionOptions&); + TransitionOptions getHeatmapColorTransition() const; + + static PropertyValue getDefaultHeatmapOpacity(); + PropertyValue getHeatmapOpacity() const; + void setHeatmapOpacity(PropertyValue); + void setHeatmapOpacityTransition(const TransitionOptions&); + TransitionOptions getHeatmapOpacityTransition() const; + + // Private implementation + + class Impl; + const Impl& impl() const; + + Mutable mutableImpl() const; + HeatmapLayer(Immutable); + std::unique_ptr cloneRef(const std::string& id) const final; +}; + +template <> +inline bool Layer::is() const { + return getType() == LayerType::Heatmap; +} + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs index 265dd57e1..6d40405cc 100644 --- a/include/mbgl/style/layers/layer.hpp.ejs +++ b/include/mbgl/style/layers/layer.hpp.ejs @@ -11,6 +11,9 @@ #include #include #include +<% if (type === 'heatmap') { -%> +#include +<% } -%> #include diff --git a/include/mbgl/style/sources/custom_geometry_source.hpp b/include/mbgl/style/sources/custom_geometry_source.hpp index a0b990b44..9daeeb381 100644 --- a/include/mbgl/style/sources/custom_geometry_source.hpp +++ b/include/mbgl/style/sources/custom_geometry_source.hpp @@ -25,6 +25,8 @@ public: double tolerance = 0.375; uint16_t tileSize = util::tileSize; uint16_t buffer = 128; + bool clip = false; + bool wrap = false; }; struct Options { diff --git a/include/mbgl/util/tileset.hpp b/include/mbgl/util/tileset.hpp index 1b7b8f0f7..ed2b90764 100644 --- a/include/mbgl/util/tileset.hpp +++ b/include/mbgl/util/tileset.hpp @@ -14,22 +14,28 @@ namespace mbgl { class Tileset { public: enum class Scheme : bool { XYZ, TMS }; + enum class DEMEncoding : bool { Mapbox, Terrarium }; std::vector tiles; Range zoomRange; std::string attribution; Scheme scheme; + // DEMEncoding is not supported by the TileJSON spec + DEMEncoding encoding; optional bounds; + Tileset(std::vector tiles_ = std::vector(), Range zoomRange_ = { 0, util::DEFAULT_MAX_ZOOM }, std::string attribution_ = {}, - Scheme scheme_ = Scheme::XYZ) + Scheme scheme_ = Scheme::XYZ, + DEMEncoding encoding_ = DEMEncoding::Mapbox) : tiles(std::move(tiles_)), zoomRange(std::move(zoomRange_)), attribution(std::move(attribution_)), scheme(scheme_), - bounds() {} + encoding(encoding_), + bounds() {}; // TileJSON also includes center and zoom but they are not used by mbgl. diff --git a/mapbox-gl-js b/mapbox-gl-js index de365184e..d61850a6b 160000 --- a/mapbox-gl-js +++ b/mapbox-gl-js @@ -1 +1 @@ -Subproject commit de365184e13c08fb42bbd93a08abfc8598294994 +Subproject commit d61850a6bd84ce6d697be392c7ef6d4b5555c20e diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 7b77582dd..fa9b1d29c 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,6 +2,10 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. +## master + + - HeatmapLayer [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046) + ## 6.0.0-beta.2 - February 13, 2018 - Deprecate LocationEngine [#11185](https://github.com/mapbox/mapbox-gl-native/pull/11185) - Remove LOST from SDK [11186](https://github.com/mapbox/mapbox-gl-native/pull/11186) @@ -22,7 +26,7 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to ## 5.4.0 - January 30, 2018 - Blacklist Adreno 2xx GPU for VAO support [#11047](https://github.com/mapbox/mapbox-gl-native/pull/11047) - Bearing tracking mode GPS_NORTH_FACING [#11095](https://github.com/mapbox/mapbox-gl-native/pull/11095) - - Disable logging missing location permissons [#11084](https://github.com/mapbox/mapbox-gl-native/pull/11084) + - Disable logging for missing location permissions when location is disabled [#11084](https://github.com/mapbox/mapbox-gl-native/pull/11084) - Create offline handler using the main thread looper [#11021](https://github.com/mapbox/mapbox-gl-native/pull/11021) ## 6.0.0-beta.1 - January 26, 2018 diff --git a/platform/android/DISTRIBUTE.md b/platform/android/DISTRIBUTE.md index 648f26ce2..1c6174832 100644 --- a/platform/android/DISTRIBUTE.md +++ b/platform/android/DISTRIBUTE.md @@ -13,7 +13,7 @@ BUILDTYPE=Debug or BUILDTYPE=Release ##### Creating an Android Archive file that supports all ABIs ```sh -BUILDTYPE=RELEASE make apackage +BUILDTYPE=Release make apackage ``` This will build native libraries to support following ABIs: diff --git a/platform/android/MapboxGLAndroidSDK/gradle.properties b/platform/android/MapboxGLAndroidSDK/gradle.properties index f00eb1d77..3796173fd 100644 --- a/platform/android/MapboxGLAndroidSDK/gradle.properties +++ b/platform/android/MapboxGLAndroidSDK/gradle.properties @@ -1,5 +1,5 @@ GROUP=com.mapbox.mapboxsdk -VERSION_NAME=6.0.0-SNAPSHOT +VERSION_NAME=7.0.0-SNAPSHOT POM_DESCRIPTION=Mapbox GL Android SDK POM_URL=https://github.com/mapbox/mapbox-gl-native diff --git a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro index b5a1d82c8..3b8adac5a 100644 --- a/platform/android/MapboxGLAndroidSDK/proguard-rules.pro +++ b/platform/android/MapboxGLAndroidSDK/proguard-rules.pro @@ -12,4 +12,7 @@ # config for okhttp 3.8.0, https://github.com/square/okhttp/pull/3354 -dontwarn okio.** -dontwarn javax.annotation.Nullable --dontwarn javax.annotation.ParametersAreNonnullByDefault \ No newline at end of file +-dontwarn javax.annotation.ParametersAreNonnullByDefault + +# config for optional location provider https://github.com/mapbox/mapbox-gl-native/issues/10960 +-dontwarn com.mapzen.android.lost.api** \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java index a921d1403..cf70300c6 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapView.java @@ -42,6 +42,8 @@ import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.mapboxsdk.net.ConnectivityReceiver; import com.mapbox.mapboxsdk.storage.FileSource; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; @@ -50,9 +52,6 @@ import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - import timber.log.Timber; import static com.mapbox.mapboxsdk.maps.widgets.CompassView.TIME_MAP_NORTH_ANIMATION; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java index fb2c70cb7..67ecf072e 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/widgets/CompassView.java @@ -7,10 +7,10 @@ import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPropertyAnimatorCompat; import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; +import android.support.v7.widget.AppCompatImageView; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import com.mapbox.mapboxsdk.maps.MapboxMap; @@ -24,7 +24,7 @@ import com.mapbox.mapboxsdk.maps.MapboxMap; *

*/ @SuppressLint("AppCompatCustomView") -public final class CompassView extends ImageView implements Runnable { +public final class CompassView extends AppCompatImageView implements Runnable { public static final long TIME_WAIT_IDLE = 500; public static final long TIME_MAP_NORTH_ANIMATION = 150; diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java new file mode 100644 index 000000000..6b8fd65de --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HeatmapLayer.java @@ -0,0 +1,221 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +package com.mapbox.mapboxsdk.style.layers; + +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; + +import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; + +import com.mapbox.mapboxsdk.style.layers.TransitionOptions; + +/** + * A heatmap. + * + * @see The online documentation + */ +@UiThread +public class HeatmapLayer extends Layer { + + /** + * Creates a HeatmapLayer. + * + * @param nativePtr pointer used by core + */ + public HeatmapLayer(long nativePtr) { + super(nativePtr); + } + + /** + * Creates a HeatmapLayer. + * + * @param layerId the id of the layer + * @param sourceId the id of the source + */ + public HeatmapLayer(String layerId, String sourceId) { + initialize(layerId, sourceId); + } + + protected native void initialize(String layerId, String sourceId); + + /** + * Set the source layer. + * + * @param sourceLayer the source layer to set + */ + public void setSourceLayer(String sourceLayer) { + nativeSetSourceLayer(sourceLayer); + } + + /** + * Set the source Layer. + * + * @param sourceLayer the source layer to set + * @return This + */ + public HeatmapLayer withSourceLayer(String sourceLayer) { + setSourceLayer(sourceLayer); + return this; + } + + /** + * Get the source layer. + * + * @return sourceLayer the source layer to get + */ + public String getSourceLayer() { + return nativeGetSourceLayer(); + } + + /** + * Set a single filter. + * + * @param filter the filter to set + */ + public void setFilter(Filter.Statement filter) { + nativeSetFilter(filter.toArray()); + } + + /** + * Set a single filter. + * + * @param filter the filter to set + * @return This + */ + public HeatmapLayer withFilter(Filter.Statement filter) { + setFilter(filter); + return this; + } + + /** + * Set a property or properties. + * + * @param properties the var-args properties + * @return This + */ + public HeatmapLayer withProperties(@NonNull PropertyValue... properties) { + setProperties(properties); + return this; + } + + // Property getters + + /** + * Get the HeatmapRadius property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapRadius() { + return (PropertyValue) new PropertyValue("heatmap-radius", nativeGetHeatmapRadius()); + } + + /** + * Get the HeatmapRadius property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapRadiusTransition() { + return nativeGetHeatmapRadiusTransition(); + } + + /** + * Set the HeatmapRadius property transition options + * + * @param options transition options for Float + */ + public void setHeatmapRadiusTransition(TransitionOptions options) { + nativeSetHeatmapRadiusTransition(options.getDuration(), options.getDelay()); + } + + /** + * Get the HeatmapWeight property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapWeight() { + return (PropertyValue) new PropertyValue("heatmap-weight", nativeGetHeatmapWeight()); + } + + /** + * Get the HeatmapIntensity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapIntensity() { + return (PropertyValue) new PropertyValue("heatmap-intensity", nativeGetHeatmapIntensity()); + } + + /** + * Get the HeatmapIntensity property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapIntensityTransition() { + return nativeGetHeatmapIntensityTransition(); + } + + /** + * Set the HeatmapIntensity property transition options + * + * @param options transition options for Float + */ + public void setHeatmapIntensityTransition(TransitionOptions options) { + nativeSetHeatmapIntensityTransition(options.getDuration(), options.getDelay()); + } + + /** + * Get the HeatmapOpacity property + * + * @return property wrapper value around Float + */ + @SuppressWarnings("unchecked") + public PropertyValue getHeatmapOpacity() { + return (PropertyValue) new PropertyValue("heatmap-opacity", nativeGetHeatmapOpacity()); + } + + /** + * Get the HeatmapOpacity property transition options + * + * @return transition options for Float + */ + public TransitionOptions getHeatmapOpacityTransition() { + return nativeGetHeatmapOpacityTransition(); + } + + /** + * Set the HeatmapOpacity property transition options + * + * @param options transition options for Float + */ + public void setHeatmapOpacityTransition(TransitionOptions options) { + nativeSetHeatmapOpacityTransition(options.getDuration(), options.getDelay()); + } + + private native Object nativeGetHeatmapRadius(); + + private native TransitionOptions nativeGetHeatmapRadiusTransition(); + + private native void nativeSetHeatmapRadiusTransition(long duration, long delay); + + private native Object nativeGetHeatmapWeight(); + + private native Object nativeGetHeatmapIntensity(); + + private native TransitionOptions nativeGetHeatmapIntensityTransition(); + + private native void nativeSetHeatmapIntensityTransition(long duration, long delay); + + private native Object nativeGetHeatmapOpacity(); + + private native TransitionOptions nativeGetHeatmapOpacityTransition(); + + private native void nativeSetHeatmapOpacityTransition(long duration, long delay); + + @Override + protected native void finalize() throws Throwable; + +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java index dc65cb508..7e3d3a777 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java @@ -11,7 +11,7 @@ import static com.mapbox.mapboxsdk.utils.ColorUtils.rgbaToColor; import com.mapbox.mapboxsdk.style.layers.TransitionOptions; /** - * Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB tiles + * Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB and Mapzen Terrarium tiles. * * @see The online documentation */ diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java index 6e644c559..18ee05e63 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java @@ -1515,6 +1515,138 @@ public class PropertyFactory { return new PaintPropertyValue<>("circle-stroke-opacity", function); } + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapRadius(Float value) { + return new PaintPropertyValue<>("heatmap-radius", value); + } + + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapRadius(Expression expression) { + return new PaintPropertyValue<>("heatmap-radius", expression); + } + + + /** + * Radius of influence of one heatmap point in density-independent pixels. Increasing the value makes the heatmap smoother, but less detailed. + * + * @param the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapRadius(Function function) { + return new PaintPropertyValue<>("heatmap-radius", function); + } + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapWeight(Float value) { + return new PaintPropertyValue<>("heatmap-weight", value); + } + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapWeight(Expression expression) { + return new PaintPropertyValue<>("heatmap-weight", expression); + } + + + /** + * A measure of how much an individual point contributes to the heatmap. A value of 10 would be equivalent to having 10 points of weight 1 in the same spot. Especially useful when combined with clustering. + * + * @param the function input type + * @param function a wrapper function for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapWeight(Function function) { + return new PaintPropertyValue<>("heatmap-weight", function); + } + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapIntensity(Float value) { + return new PaintPropertyValue<>("heatmap-intensity", value); + } + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapIntensity(Expression expression) { + return new PaintPropertyValue<>("heatmap-intensity", expression); + } + + + /** + * Similar to {@link PropertyFactory#heatmapWeight} but controls the intensity of the heatmap globally. Primarily used for adjusting the heatmap based on zoom level. + * + * @param the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapIntensity(CameraFunction function) { + return new PaintPropertyValue<>("heatmap-intensity", function); + } + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue heatmapOpacity(Float value) { + return new PaintPropertyValue<>("heatmap-opacity", value); + } + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue heatmapOpacity(Expression expression) { + return new PaintPropertyValue<>("heatmap-opacity", expression); + } + + + /** + * The global opacity at which the heatmap layer will be drawn. + * + * @param the zoom parameter type + * @param function a wrapper {@link CameraFunction} for Float + * @return property wrapper around a Float function + */ + @Deprecated + public static PropertyValue> heatmapOpacity(CameraFunction function) { + return new PaintPropertyValue<>("heatmap-opacity", function); + } + /** * The opacity of the entire fill extrusion layer. This is rendered on a per-layer, not per-feature, basis, and data-driven styling is not available. * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java index 3467ed7a7..1b0999ae2 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java @@ -37,18 +37,18 @@ public class CustomGeometrySource extends Source { * @param provider The tile provider that returns geometry data for this source. */ public CustomGeometrySource(String id, GeometryTileProvider provider) { - this(id, provider, new GeoJsonOptions()); + this(id, provider, new CustomGeometrySourceOptions()); } /** - * Create a CustomGeometrySource with non-default GeoJsonOptions. + * Create a CustomGeometrySource with non-default CustomGeometrySourceOptions. *

Supported options are minZoom, maxZoom, buffer, and tolerance.

* * @param id The source id. * @param provider The tile provider that returns geometry data for this source. - * @param options GeoJsonOptions. + * @param options CustomGeometrySourceOptions. */ - public CustomGeometrySource(String id, GeometryTileProvider provider, GeoJsonOptions options) { + public CustomGeometrySource(String id, GeometryTileProvider provider, CustomGeometrySourceOptions options) { this.provider = provider; executor = Executors.newFixedThreadPool(4); initialize(id, options); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java new file mode 100644 index 000000000..4ada38c23 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySourceOptions.java @@ -0,0 +1,31 @@ +package com.mapbox.mapboxsdk.style.sources; + +/** + * Builder class for composing CustomGeometrySource objects. + */ +public class CustomGeometrySourceOptions extends GeoJsonOptions { + + /** + * If the data includes wrapped coordinates, setting this to true unwraps the coordinates. + * + * @param wrap defaults to false + * @return the current instance for chaining + */ + public CustomGeometrySourceOptions withWrap(boolean wrap) { + this.put("wrap", wrap); + return this; + } + + /** + * If the data includes geometry outside the tile boundaries, setting this to true clips the geometry + * to the tile boundaries. + * + * @param clip defaults to false + * @return the current instance for chaining + */ + public CustomGeometrySourceOptions withClip(boolean clip) { + this.put("clip", clip); + return this; + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java new file mode 100644 index 000000000..364c8d267 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HeatmapLayerTest.java @@ -0,0 +1,626 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +package com.mapbox.mapboxsdk.testapp.style; + +import android.graphics.Color; +import android.support.test.espresso.UiController; +import android.support.test.runner.AndroidJUnit4; + +import timber.log.Timber; + +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.style.functions.CompositeFunction; +import com.mapbox.mapboxsdk.style.functions.CameraFunction; +import com.mapbox.mapboxsdk.style.functions.SourceFunction; +import com.mapbox.mapboxsdk.style.functions.stops.CategoricalStops; +import com.mapbox.mapboxsdk.style.functions.stops.ExponentialStops; +import com.mapbox.mapboxsdk.style.functions.stops.IdentityStops; +import com.mapbox.mapboxsdk.style.functions.stops.IntervalStops; +import com.mapbox.mapboxsdk.style.functions.stops.Stop; +import com.mapbox.mapboxsdk.style.functions.stops.Stops; +import com.mapbox.mapboxsdk.style.layers.HeatmapLayer; +import com.mapbox.mapboxsdk.testapp.action.MapboxMapAction; +import com.mapbox.mapboxsdk.testapp.activity.BaseActivityTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.mapbox.mapboxsdk.style.functions.Function.*; +import static com.mapbox.mapboxsdk.style.functions.stops.Stop.stop; +import static com.mapbox.mapboxsdk.style.functions.stops.Stops.*; +import static com.mapbox.mapboxsdk.testapp.action.MapboxMapAction.invoke; +import static org.junit.Assert.*; +import static com.mapbox.mapboxsdk.style.layers.Property.*; +import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.*; + +import com.mapbox.mapboxsdk.style.layers.TransitionOptions; +import com.mapbox.mapboxsdk.testapp.activity.espresso.EspressoTestActivity; + +/** + * Basic smoke tests for HeatmapLayer + */ +@RunWith(AndroidJUnit4.class) +public class HeatmapLayerTest extends BaseActivityTest { + + private HeatmapLayer layer; + + @Override + protected Class getActivityClass() { + return EspressoTestActivity.class; + } + + private void setupLayer() { + Timber.i("Retrieving layer"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + if ((layer = mapboxMap.getLayerAs("my-layer")) == null) { + Timber.i("Adding layer"); + layer = new HeatmapLayer("my-layer", "composite"); + layer.setSourceLayer("composite"); + mapboxMap.addLayer(layer); + // Layer reference is now stale, get new reference + layer = mapboxMap.getLayerAs("my-layer"); + } + } + }); + } + + @Test + public void testSetVisibility() { + validateTestSetup(); + setupLayer(); + Timber.i("Visibility"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getVisibility().getValue(), VISIBLE); + + // Set + layer.setProperties(visibility(NONE)); + assertEquals(layer.getVisibility().getValue(), NONE); + } + }); + } + + @Test + public void testSourceLayer() { + validateTestSetup(); + setupLayer(); + Timber.i("SourceLayer"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Get initial + assertEquals(layer.getSourceLayer(), "composite"); + + // Set + final String sourceLayer = "test"; + layer.setSourceLayer(sourceLayer); + assertEquals(layer.getSourceLayer(), sourceLayer); + } + }); + } + + @Test + public void testHeatmapRadiusTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radiusTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapRadiusTransition(options); + assertEquals(layer.getHeatmapRadiusTransition(), options); + } + }); + } + + @Test + public void testHeatmapRadiusAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapRadius(0.3f)); + assertEquals((Float) layer.getHeatmapRadius().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapRadiusAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + zoom( + exponential( + stop(2, heatmapRadius(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapRadiusAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius(property("FeaturePropertyA", Stops.identity())) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapRadiusAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, heatmapRadius(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapRadiusAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, heatmapRadius(0.3f)) + ) + ).withDefaultValue(heatmapRadius(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getHeatmapRadius().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testHeatmapRadiusAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-radius"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapRadius( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, heatmapRadius(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(heatmapRadius(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapRadius()); + assertNotNull(layer.getHeatmapRadius().getFunction()); + assertEquals(CompositeFunction.class, layer.getHeatmapRadius().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapRadius().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapRadius().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getHeatmapRadius().getFunction().getStops()).size()); + + ExponentialStops, Float> stops = + (ExponentialStops, Float>) layer.getHeatmapRadius().getFunction().getStops(); + Stop, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test + public void testHeatmapWeightAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapWeight(0.3f)); + assertEquals((Float) layer.getHeatmapWeight().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapWeightAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + zoom( + exponential( + stop(2, heatmapWeight(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapWeightAsIdentitySourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight(property("FeaturePropertyA", Stops.identity())) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(IdentityStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapWeightAsExponentialSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + property( + "FeaturePropertyA", + exponential( + stop(0.3f, heatmapWeight(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + } + }); + } + + @Test + public void testHeatmapWeightAsCategoricalSourceFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + property( + "FeaturePropertyA", + categorical( + stop(1.0f, heatmapWeight(0.3f)) + ) + ).withDefaultValue(heatmapWeight(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(SourceFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((SourceFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(CategoricalStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue()); + assertNotNull(((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue()); + assertEquals(0.3f, ((SourceFunction) layer.getHeatmapWeight().getFunction()).getDefaultValue().getValue()); + } + }); + + } + + @Test + public void testHeatmapWeightAsCompositeFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-weight"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapWeight( + composite( + "FeaturePropertyA", + exponential( + stop(0, 0.3f, heatmapWeight(0.9f)) + ).withBase(0.5f) + ).withDefaultValue(heatmapWeight(0.3f)) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapWeight()); + assertNotNull(layer.getHeatmapWeight().getFunction()); + assertEquals(CompositeFunction.class, layer.getHeatmapWeight().getFunction().getClass()); + assertEquals("FeaturePropertyA", ((CompositeFunction) layer.getHeatmapWeight().getFunction()).getProperty()); + assertEquals(ExponentialStops.class, layer.getHeatmapWeight().getFunction().getStops().getClass()); + assertEquals(1, ((ExponentialStops) layer.getHeatmapWeight().getFunction().getStops()).size()); + + ExponentialStops, Float> stops = + (ExponentialStops, Float>) layer.getHeatmapWeight().getFunction().getStops(); + Stop, Float> stop = stops.iterator().next(); + assertEquals(0f, stop.in.zoom, 0.001); + assertEquals(0.3f, stop.in.value, 0.001f); + assertEquals(0.9f, stop.out, 0.001f); + } + }); + } + + @Test + public void testHeatmapIntensityTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensityTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapIntensityTransition(options); + assertEquals(layer.getHeatmapIntensityTransition(), options); + } + }); + } + + @Test + public void testHeatmapIntensityAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapIntensity(0.3f)); + assertEquals((Float) layer.getHeatmapIntensity().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapIntensityAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-intensity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapIntensity( + zoom( + exponential( + stop(2, heatmapIntensity(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapIntensity()); + assertNotNull(layer.getHeatmapIntensity().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapIntensity().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapIntensity().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapIntensity().getFunction().getStops()).size()); + } + }); + } + + @Test + public void testHeatmapOpacityTransition() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacityTransitionOptions"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setHeatmapOpacityTransition(options); + assertEquals(layer.getHeatmapOpacityTransition(), options); + } + }); + } + + @Test + public void testHeatmapOpacityAsConstant() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set and Get + layer.setProperties(heatmapOpacity(0.3f)); + assertEquals((Float) layer.getHeatmapOpacity().getValue(), (Float) 0.3f); + } + }); + } + + @Test + public void testHeatmapOpacityAsCameraFunction() { + validateTestSetup(); + setupLayer(); + Timber.i("heatmap-opacity"); + invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() { + @Override + public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) { + assertNotNull(layer); + + // Set + layer.setProperties( + heatmapOpacity( + zoom( + exponential( + stop(2, heatmapOpacity(0.3f)) + ).withBase(0.5f) + ) + ) + ); + + // Verify + assertNotNull(layer.getHeatmapOpacity()); + assertNotNull(layer.getHeatmapOpacity().getFunction()); + assertEquals(CameraFunction.class, layer.getHeatmapOpacity().getFunction().getClass()); + assertEquals(ExponentialStops.class, layer.getHeatmapOpacity().getFunction().getStops().getClass()); + assertEquals(0.5f, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).getBase(), 0.001); + assertEquals(1, ((ExponentialStops) layer.getHeatmapOpacity().getFunction().getStops()).size()); + } + }); + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 9e64e03e1..0b6d2a155 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -800,6 +800,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity"/> + + + + { + mapboxMap = map; + addEarthquakeSource(); + addHeatmapLayer(); + addCircleLayer(); + }); + } + + private void addEarthquakeSource() { + try { + mapboxMap.addSource(new GeoJsonSource(EARTHQUAKE_SOURCE_ID, new URL(EARTHQUAKE_SOURCE_URL))); + } catch (MalformedURLException malformedUrlException) { + Timber.e(malformedUrlException, "That's not an url... "); + } + } + + private void addHeatmapLayer() { + HeatmapLayer layer = new HeatmapLayer(HEATMAP_LAYER_ID, EARTHQUAKE_SOURCE_ID); + layer.setMaxZoom(9); + layer.setSourceLayer(HEATMAP_LAYER_SOURCE); + layer.setProperties( + + // TODO add heatmap color https://github.com/mapbox/mapbox-gl-native/issues/11172 + // Color ramp for heatmap. Domain is 0 (low) to 1 (high). + // Begin color ramp at 0-stop with a 0-transparancy color + // to create a blur-like effect. + //heatmapColor(), + + // Increase the heatmap weight based on frequency and property magnitude + heatmapWeight( + interpolate( + linear(), get("mag"), + stop(0, 0), + stop(6, 1) + ) + ), + + // Increase the heatmap color weight weight by zoom level + // heatmap-intensity is a multiplier on top of heatmap-weight + heatmapIntensity( + interpolate( + linear(), zoom(), + stop(0, 1), + stop(9, 3) + ) + ), + + // Adjust the heatmap radius by zoom level + heatmapRadius( + interpolate( + linear(), zoom(), + stop(0, 2), + stop(9, 20) + ) + ), + + // Transition from heatmap to circle layer by zoom level + heatmapOpacity( + interpolate( + linear(), zoom(), + stop(7, 1), + stop(9, 0) + ) + ) + ); + + mapboxMap.addLayerAbove(layer, "waterway-label"); + } + + private void addCircleLayer() { + CircleLayer circleLayer = new CircleLayer(CIRCLE_LAYER_ID, EARTHQUAKE_SOURCE_ID); + circleLayer.setProperties( + + // Size circle radius by earthquake magnitude and zoom level + circleRadius( + interpolate( + linear(), zoom(), + literal(7), interpolate( + linear(), get("mag"), + stop(1, 1), + stop(6, 4) + ), + literal(16), interpolate( + linear(), get("mag"), + stop(1, 5), + stop(6, 50) + ) + ) + ), + + // Color circle by earthquake magnitude + circleColor( + interpolate( + linear(), get("mag"), + literal(1), rgba(33, 102, 172, 0), + literal(2), rgb(103, 169, 207), + literal(3), rgb(209, 229, 240), + literal(4), rgb(253, 219, 199), + literal(5), rgb(239, 138, 98), + literal(6), rgb(178, 24, 43) + ) + ), + + // Transition from heatmap to circle layer by zoom level + circleOpacity( + interpolate( + linear(), zoom(), + stop(7, 0), + stop(8, 1) + ) + ), + circleStrokeColor("white"), + circleStrokeWidth(1.0f) + ); + + mapboxMap.addLayerBelow(circleLayer, HEATMAP_LAYER_ID); + } + + @Override + protected void onStart() { + super.onStart(); + mapView.onStart(); + } + + @Override + protected void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onStop() { + super.onStop(); + mapView.onStop(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml new file mode 100644 index 000000000..23ef9ea33 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_heatmaplayer.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 51182f958..fc0485129 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -71,4 +71,5 @@ Example Custom Geometry Source Suzhou using Droid Sans for Chinese glyphs Example raster-dem source and hillshade layer + Use HeatmapLayer to visualise earthquakes diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml index 078ccb64a..e2bbf5af0 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/titles.xml @@ -71,4 +71,5 @@ Grid Source Local CJK glyph generation Hillshade + Heatmap layer \ No newline at end of file diff --git a/platform/android/config.cmake b/platform/android/config.cmake index e1c36789f..30182bbc0 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -158,6 +158,8 @@ add_library(mbgl-android STATIC platform/android/src/style/layers/fill_extrusion_layer.hpp platform/android/src/style/layers/fill_layer.cpp platform/android/src/style/layers/fill_layer.hpp + platform/android/src/style/layers/heatmap_layer.cpp + platform/android/src/style/layers/heatmap_layer.hpp platform/android/src/style/layers/hillshade_layer.cpp platform/android/src/style/layers/hillshade_layer.hpp platform/android/src/style/layers/layer.cpp diff --git a/platform/android/scripts/exclude-activity-gen.json b/platform/android/scripts/exclude-activity-gen.json index 2170c4d9f..1d61d9394 100644 --- a/platform/android/scripts/exclude-activity-gen.json +++ b/platform/android/scripts/exclude-activity-gen.json @@ -27,6 +27,7 @@ "QueryRenderedFeaturesBoxHighlightActivity", "MultiMapActivity", "MapInDialogActivity", + "SimpleMapActivity", "ManualZoomActivity", "MaxMinZoomActivity", "ScrollByActivity", @@ -34,4 +35,4 @@ "SymbolGeneratorActivity", "TextureViewTransparentBackgroundActivity", "SimpleMapActivity" -] \ No newline at end of file +] diff --git a/platform/android/scripts/generate-style-code.js b/platform/android/scripts/generate-style-code.js index 65b0a399a..6e6d3cfa6 100755 --- a/platform/android/scripts/generate-style-code.js +++ b/platform/android/scripts/generate-style-code.js @@ -28,6 +28,9 @@ var layers = Object.keys(spec.layer.type.values).map((type) => { }, []); const paintProperties = Object.keys(spec[`paint_${type}`]).reduce((memo, name) => { + // disabled for now, see https://github.com/mapbox/mapbox-gl-native/issues/11172 + if (name === 'heatmap-color') return memo; + spec[`paint_${type}`][name].name = name; memo.push(spec[`paint_${type}`][name]); return memo; diff --git a/platform/android/src/android_renderer_frontend.cpp b/platform/android/src/android_renderer_frontend.cpp index afdb08a10..2a03d9de9 100644 --- a/platform/android/src/android_renderer_frontend.cpp +++ b/platform/android/src/android_renderer_frontend.cpp @@ -77,8 +77,8 @@ void AndroidRendererFrontend::update(std::shared_ptr params) { mapRenderer.requestRender(); } -void AndroidRendererFrontend::onLowMemory() { - mapRenderer.actor().invoke(&Renderer::onLowMemory); +void AndroidRendererFrontend::reduceMemoryUse() { + mapRenderer.actor().invoke(&Renderer::reduceMemoryUse); } std::vector AndroidRendererFrontend::querySourceFeatures(const std::string& sourceID, diff --git a/platform/android/src/android_renderer_frontend.hpp b/platform/android/src/android_renderer_frontend.hpp index 178870c45..b61904e38 100644 --- a/platform/android/src/android_renderer_frontend.hpp +++ b/platform/android/src/android_renderer_frontend.hpp @@ -39,7 +39,7 @@ public: AnnotationIDs queryShapeAnnotations(const ScreenBox& box) const; // Memory - void onLowMemory(); + void reduceMemoryUse(); private: MapRenderer& mapRenderer; diff --git a/platform/android/src/example_custom_layer.cpp b/platform/android/src/example_custom_layer.cpp index 1ed68d083..6d0bd4de4 100644 --- a/platform/android/src/example_custom_layer.cpp +++ b/platform/android/src/example_custom_layer.cpp @@ -96,7 +96,7 @@ void nativeContextLost(void */*context*/) { mbgl::Log::Info(mbgl::Event::General, "nativeContextLost"); } -void nativeDenitialize(void *context) { +void nativeDeinitialize(void *context) { mbgl::Log::Info(mbgl::Event::General, "nativeDeinitialize"); delete reinterpret_cast(context); } @@ -132,7 +132,7 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) { env->SetStaticLongField(customLayerClass, env->GetStaticFieldID(customLayerClass, "DeinitializeFunction", "J"), - reinterpret_cast(nativeDenitialize)); + reinterpret_cast(nativeDeinitialize)); return JNI_VERSION_1_6; } diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index eefb578f3..3bd46631a 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -450,7 +450,7 @@ jni::Array NativeMapView::addMarkers(jni::JNIEnv& env, jni::ArrayonLowMemory(); + rendererFrontend->reduceMemoryUse(); } using DebugOptions = mbgl::MapDebugOptions; diff --git a/platform/android/src/style/layers/heatmap_layer.cpp b/platform/android/src/style/layers/heatmap_layer.cpp new file mode 100644 index 000000000..609499ec9 --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.cpp @@ -0,0 +1,134 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +#include "heatmap_layer.hpp" + +#include + +#include "../conversion/property_value.hpp" +#include "../conversion/transition_options.hpp" + +namespace mbgl { +namespace android { + + /** + * Creates an owning peer object (for layers not attached to the map) from the JVM side + */ + HeatmapLayer::HeatmapLayer(jni::JNIEnv& env, jni::String layerId, jni::String sourceId) + : Layer(env, std::make_unique(jni::Make(env, layerId), jni::Make(env, sourceId))) { + } + + /** + * Creates a non-owning peer object (for layers currently attached to the map) + */ + HeatmapLayer::HeatmapLayer(mbgl::Map& map, mbgl::style::HeatmapLayer& coreLayer) + : Layer(map, coreLayer) { + } + + /** + * Creates an owning peer object (for layers not attached to the map) + */ + HeatmapLayer::HeatmapLayer(mbgl::Map& map, std::unique_ptr coreLayer) + : Layer(map, std::move(coreLayer)) { + } + + HeatmapLayer::~HeatmapLayer() = default; + + // Property getters + + jni::Object HeatmapLayer::getHeatmapRadius(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapRadius()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapRadiusTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapRadiusTransition(); + return *convert>(env, options); + } + + void HeatmapLayer::setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as()->HeatmapLayer::setHeatmapRadiusTransition(options); + } + + jni::Object HeatmapLayer::getHeatmapWeight(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapWeight()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapIntensity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapIntensity()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapIntensityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapIntensityTransition(); + return *convert>(env, options); + } + + void HeatmapLayer::setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as()->HeatmapLayer::setHeatmapIntensityTransition(options); + } + + jni::Object HeatmapLayer::getHeatmapOpacity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + Result converted = convert(env, layer.as()->HeatmapLayer::getHeatmapOpacity()); + return jni::Object(*converted); + } + + jni::Object HeatmapLayer::getHeatmapOpacityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + mbgl::style::TransitionOptions options = layer.as()->HeatmapLayer::getHeatmapOpacityTransition(); + return *convert>(env, options); + } + + void HeatmapLayer::setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay) { + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + layer.as()->HeatmapLayer::setHeatmapOpacityTransition(options); + } + + + jni::Class HeatmapLayer::javaClass; + + jni::jobject* HeatmapLayer::createJavaPeer(jni::JNIEnv& env) { + static auto constructor = HeatmapLayer::javaClass.template GetConstructor(env); + return HeatmapLayer::javaClass.New(env, constructor, reinterpret_cast(this)); + } + + void HeatmapLayer::registerNative(jni::JNIEnv& env) { + // Lookup the class + HeatmapLayer::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); + + #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + // Register the peer + jni::RegisterNativePeer( + env, HeatmapLayer::javaClass, "nativePtr", + std::make_unique, + "initialize", + "finalize", + METHOD(&HeatmapLayer::getHeatmapRadiusTransition, "nativeGetHeatmapRadiusTransition"), + METHOD(&HeatmapLayer::setHeatmapRadiusTransition, "nativeSetHeatmapRadiusTransition"), + METHOD(&HeatmapLayer::getHeatmapRadius, "nativeGetHeatmapRadius"), + METHOD(&HeatmapLayer::getHeatmapWeight, "nativeGetHeatmapWeight"), + METHOD(&HeatmapLayer::getHeatmapIntensityTransition, "nativeGetHeatmapIntensityTransition"), + METHOD(&HeatmapLayer::setHeatmapIntensityTransition, "nativeSetHeatmapIntensityTransition"), + METHOD(&HeatmapLayer::getHeatmapIntensity, "nativeGetHeatmapIntensity"), + METHOD(&HeatmapLayer::getHeatmapOpacityTransition, "nativeGetHeatmapOpacityTransition"), + METHOD(&HeatmapLayer::setHeatmapOpacityTransition, "nativeSetHeatmapOpacityTransition"), + METHOD(&HeatmapLayer::getHeatmapOpacity, "nativeGetHeatmapOpacity")); + } + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/layers/heatmap_layer.hpp b/platform/android/src/style/layers/heatmap_layer.hpp new file mode 100644 index 000000000..85f9f0292 --- /dev/null +++ b/platform/android/src/style/layers/heatmap_layer.hpp @@ -0,0 +1,50 @@ +// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`. + +#pragma once + +#include "layer.hpp" +#include "../transition_options.hpp" +#include +#include + +namespace mbgl { +namespace android { + +class HeatmapLayer : public Layer { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/layers/HeatmapLayer"; }; + + static jni::Class javaClass; + + static void registerNative(jni::JNIEnv&); + + HeatmapLayer(jni::JNIEnv&, jni::String, jni::String); + + HeatmapLayer(mbgl::Map&, mbgl::style::HeatmapLayer&); + + HeatmapLayer(mbgl::Map&, std::unique_ptr); + + ~HeatmapLayer(); + + // Properties + + jni::Object getHeatmapRadius(jni::JNIEnv&); + void setHeatmapRadiusTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object getHeatmapRadiusTransition(jni::JNIEnv&); + + jni::Object getHeatmapWeight(jni::JNIEnv&); + + jni::Object getHeatmapIntensity(jni::JNIEnv&); + void setHeatmapIntensityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object getHeatmapIntensityTransition(jni::JNIEnv&); + + jni::Object getHeatmapOpacity(jni::JNIEnv&); + void setHeatmapOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Object getHeatmapOpacityTransition(jni::JNIEnv&); + jni::jobject* createJavaPeer(jni::JNIEnv&); + +}; // class HeatmapLayer + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp index da1550bdb..29530879a 100644 --- a/platform/android/src/style/layers/layer.cpp +++ b/platform/android/src/style/layers/layer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/platform/android/src/style/layers/layers.cpp b/platform/android/src/style/layers/layers.cpp index 5d1d1bbcb..5df689b45 100644 --- a/platform/android/src/style/layers/layers.cpp +++ b/platform/android/src/style/layers/layers.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include "custom_layer.hpp" #include "fill_extrusion_layer.hpp" #include "fill_layer.hpp" +#include "heatmap_layer.hpp" #include "hillshade_layer.hpp" #include "line_layer.hpp" #include "raster_layer.hpp" @@ -32,6 +34,7 @@ template <> struct PeerType { using Type = android::Back template <> struct PeerType { using Type = android::CircleLayer; }; template <> struct PeerType { using Type = android::FillExtrusionLayer; }; template <> struct PeerType { using Type = android::FillLayer; }; +template <> struct PeerType { using Type = android::HeatmapLayer; }; template <> struct PeerType { using Type = android::HillshadeLayer; }; template <> struct PeerType { using Type = android::LineLayer; }; template <> struct PeerType { using Type = android::RasterLayer; }; @@ -95,6 +98,7 @@ void registerNativeLayers(jni::JNIEnv& env) { CustomLayer::registerNative(env); FillExtrusionLayer::registerNative(env); FillLayer::registerNative(env); + HeatmapLayer::registerNative(env); HillshadeLayer::registerNative(env); LineLayer::registerNative(env); RasterLayer::registerNative(env); diff --git a/platform/android/src/style/sources/custom_geometry_source.cpp b/platform/android/src/style/sources/custom_geometry_source.cpp index a60b962e4..b38405a3b 100644 --- a/platform/android/src/style/sources/custom_geometry_source.cpp +++ b/platform/android/src/style/sources/custom_geometry_source.cpp @@ -18,7 +18,7 @@ namespace mbgl { namespace android { // This conversion is expected not to fail because it's used only in contexts where - // the value was originally a GeoJsonOptions object on the Java side. If it fails + // the value was originally a CustomGeometrySourceOptions object on the Java side. If it fails // to convert, it's a bug in our serialization or Java-side static typing. static style::CustomGeometrySource::Options convertCustomGeometrySourceOptions(jni::JNIEnv& env, jni::Object<> options, diff --git a/platform/darwin/docs/guides/For Style Authors.md.ejs b/platform/darwin/docs/guides/For Style Authors.md.ejs index fc259e4da..37af150ac 100644 --- a/platform/darwin/docs/guides/For Style Authors.md.ejs +++ b/platform/darwin/docs/guides/For Style Authors.md.ejs @@ -160,6 +160,7 @@ the following terms for concepts defined in the style specification: In the style specification | In the SDK ---------------------------|--------- +bounds | coordinate bounds filter | predicate function type | interpolation mode id | identifier @@ -200,6 +201,7 @@ In style JSON | In TileJSON | In the SDK `tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]` `minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel` `maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel` +`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds` `tileSize` | — | `MGLTileSourceOptionTileSize` `attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security) `scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem` diff --git a/platform/darwin/scripts/generate-style-code.js b/platform/darwin/scripts/generate-style-code.js index f919a649c..8e82a8eb6 100755 --- a/platform/darwin/scripts/generate-style-code.js +++ b/platform/darwin/scripts/generate-style-code.js @@ -318,14 +318,15 @@ global.propertyDoc = function (propertyName, property, layerType, kind) { doc += '* Predefined functions, including mathematical and string operators\n' + '* Conditional expressions\n' + '* Variable assignments and references to assigned variables\n'; + const inputVariable = property.name === 'heatmap-color' ? '$heatmapDensity' : '$zoomLevel'; if (property["property-function"]) { - doc += '* Interpolation and step functions applied to the `$zoomLevel` variable and/or feature attributes\n'; + doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable and/or feature attributes\n`; } else if (property.function === "interpolated") { - doc += '* Interpolation and step functions applied to the `$zoomLevel` variable\n\n' + + doc += `* Interpolation and step functions applied to the \`${inputVariable}\` variable\n\n` + 'This property does not support applying interpolation or step functions to feature attributes.'; } else { - doc += '* Step functions applied to the `$zoomLevel` variable\n\n' + - 'This property does not support applying interpolation functions to the `$zoomLevel` variable or applying interpolation or step functions to feature attributes.'; + doc += `* Step functions applied to the \`${inputVariable}\` variable\n\n` + + `This property does not support applying interpolation functions to the \`${inputVariable}\` variable or applying interpolation or step functions to feature attributes.`; } } return doc; @@ -397,7 +398,7 @@ global.describeValue = function (value, property, layerType) { throw new Error(`No description available for ${value[0]} expression in ${property.name} of ${layerType}.`); } } - + switch (property.type) { case 'boolean': return value ? '`YES`' : '`NO`'; diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json index 12cfa3157..b0c50a06f 100644 --- a/platform/darwin/scripts/style-spec-overrides-v8.json +++ b/platform/darwin/scripts/style-spec-overrides-v8.json @@ -23,6 +23,9 @@ "circle": { "doc": "An `MGLCircleStyleLayer` is a style layer that renders one or more filled circles on the map.\n\nUse a circle style layer to configure the visual appearance of point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nA circle style layer renders circles whose radii are measured in screen units. To display circles on the map whose radii correspond to real-world distances, use many-sided regular polygons and configure their appearance using an `MGLFillStyleLayer` object." }, + "heatmap": { + "doc": "An `MGLHeatmapStyleLayer` is a style layer that renders a heatmap.\n\nA heatmap visualizes the spatial distribution of a large, dense set of point data, using color to avoid cluttering the map with individual points at low zoom levels. The points are weighted by an attribute you specify. Use a heatmap style layer in conjunction with point or point collection features in vector tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` instances in an `MGLShapeSource` object.\n\nConsider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can alternatively cluster the source using the `MGLShapeSourceOptionClustered` option and render the data using an `MGLCircleStyleLayer` or `MGLSymbolStyleLayer`." + }, "raster": { "doc": "An `MGLRasterStyleLayer` is a style layer that renders georeferenced raster imagery on the map, especially raster tiles.\n\nUse a raster style layer to configure the color parameters of raster tiles loaded by an `MGLRasterSource` object or raster images loaded by an `MGLImageSource` object. For example, you could use a raster style layer to render Mapbox Satellite imagery, a raster tile set uploaded to Mapbox Studio, or a raster map authored in TileMill, the classic Mapbox Editor, or Mapbox Studio Classic.\n\nRaster images may also be used as icons or patterns in a style layer. To register an image for use as an icon or pattern, use the `-[MGLStyle setImage:forName:]` method. To configure a point annotation’s image, use the `MGLAnnotationImage` class." }, @@ -79,6 +82,11 @@ "doc": "The base color of this layer. The extrusion's surfaces will be shaded differently based on this color in combination with the `light` settings. If this color is specified with an alpha component, the alpha component will be ignored; use `fill-extrusion-opacity` to set layer opacityco." } }, + "paint_heatmap": { + "heatmap-color": { + "doc": "Defines the color of each pixel based on its density value in a heatmap. Should be an expression that uses `$heatmapDensity` as input." + } + }, "paint_line": { "line-pattern": { "doc": "Name of image in style images to use for drawing image lines. For seamless patterns, image width must be a factor of two (2, 4, 8, ..., 512)." @@ -114,4 +122,4 @@ "doc": "Distance that the text's anchor is moved from its original placement." } } -} \ No newline at end of file +} diff --git a/platform/darwin/src/MGLAbstractShapeSource.h b/platform/darwin/src/MGLAbstractShapeSource.h index 3b35986b3..d61f41fbf 100644 --- a/platform/darwin/src/MGLAbstractShapeSource.h +++ b/platform/darwin/src/MGLAbstractShapeSource.h @@ -81,6 +81,31 @@ extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionBuffer; */ extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; +/** + An `NSNumber` object containing a Boolean value; specifies whether the shape of + an `MGLComputedShapeSource` should be wrapped to accomodate coordinates with + longitudes beyond −180 and 180. The default value is `NO`. + + Setting this option to `YES` affects rendering performance. + + This option is ignored when creating an instance of a class besides + `MGLComputedShapeSource`. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionWrapsCoordinates; + +/** + An `NSNumber` object containing a Boolean value; specifies whether the shape of + an `MGLComputedShapeSource` should be clipped at the edge of each tile. The + default value is `NO`. + + Setting this option to `YES` affects rendering performance. Use this option to + clip `MGLPolyline`s and `MGLPolygon`s at tile boundaries without artifacts. + + This option is ignored when creating an instance of a class besides + `MGLComputedShapeSource`. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClipsCoordinates; + /** `MGLAbstractShapeSource` is an abstract base class for map content sources that supply vector shapes to be shown on the map. A shape source is added to an diff --git a/platform/darwin/src/MGLAbstractShapeSource.mm b/platform/darwin/src/MGLAbstractShapeSource.mm index 755d04038..eb0807cee 100644 --- a/platform/darwin/src/MGLAbstractShapeSource.mm +++ b/platform/darwin/src/MGLAbstractShapeSource.mm @@ -8,6 +8,8 @@ const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSour const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering"; const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel = @"MGLShapeSourceOptionMinimumZoomLevel"; const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance"; +const MGLShapeSourceOption MGLShapeSourceOptionWrapsCoordinates = @"MGLShapeSourceOptionWrapsCoordinates"; +const MGLShapeSourceOption MGLShapeSourceOptionClipsCoordinates = @"MGLShapeSourceOptionClipsCoordinates"; @interface MGLAbstractShapeSource () @@ -113,5 +115,22 @@ mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDi } sourceOptions.tileOptions.tolerance = value.doubleValue; } + + if (NSNumber *value = options[MGLShapeSourceOptionWrapsCoordinates]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionWrapsCoordinates must be an NSNumber."]; + } + sourceOptions.tileOptions.wrap = value.boolValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionClipsCoordinates]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClipsCoordinates must be an NSNumber."]; + } + sourceOptions.tileOptions.clip = value.boolValue; + } + return sourceOptions; } diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.h b/platform/darwin/src/MGLHeatmapStyleLayer.h new file mode 100644 index 000000000..35095fd52 --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.h @@ -0,0 +1,189 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLFoundation.h" +#import "MGLVectorStyleLayer.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + An `MGLHeatmapStyleLayer` is a style layer that renders a heatmap. + + A heatmap visualizes the spatial distribution of a large, dense set of point + data, using color to avoid cluttering the map with individual points at low + zoom levels. The points are weighted by an attribute you specify. Use a heatmap + style layer in conjunction with point or point collection features in vector + tiles loaded by an `MGLVectorSource` object or `MGLPointAnnotation`, + `MGLPointFeature`, `MGLPointCollection`, or `MGLPointCollectionFeature` + instances in an `MGLShapeSource` object. + + Consider accompanying a heatmap style layer with an `MGLCircleStyleLayer` or + `MGLSymbolStyleLayer` at high zoom levels. If you are unsure whether the point + data in an `MGLShapeSource` is dense enough to warrant a heatmap, you can + alternatively cluster the source using the `MGLShapeSourceOptionClustered` + option and render the data using an `MGLCircleStyleLayer` or + `MGLSymbolStyleLayer`. + + You can access an existing heatmap style layer using the + `-[MGLStyle layerWithIdentifier:]` method if you know its identifier; + otherwise, find it using the `MGLStyle.layers` property. You can also create a + new heatmap style layer and add it to the style using a method such as + `-[MGLStyle addLayer:]`. + + ### Example + + ```swift + let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) + layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 0, + 6: 1]) + layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 1, + 9: 3]) + mapView.style?.addLayer(layer) + ``` + */ +MGL_EXPORT +@interface MGLHeatmapStyleLayer : MGLVectorStyleLayer + +/** + Returns a heatmap style layer initialized with an identifier and source. + + After initializing and configuring the style layer, add it to a map view’s + style using the `-[MGLStyle addLayer:]` or + `-[MGLStyle insertLayer:belowLayer:]` method. + + @param identifier A string that uniquely identifies the source in the style to + which it is added. + @param source The source from which to obtain the data to style. If the source + has not yet been added to the current style, the behavior is undefined. + @return An initialized foreground style layer. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source; + +#pragma mark - Accessing the Paint Attributes + +/** + Defines the color of each point based on its density value in a heatmap. Should + be an expression that uses `$heatmapDensity` as input. + + The default value of this property is an expression that evaluates to a rainbow + color scale from blue to red. Set this property to `nil` to reset it to the + default value. + + You can set this property to an expression containing any of the following: + + * Constant `UIColor` values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$heatmapDensity` variable + + This property does not support applying interpolation or step functions to + feature attributes. + */ +@property (nonatomic, null_resettable) NSExpression *heatmapColor; + +/** + Similar to `heatmapWeight` but controls the intensity of the heatmap globally. + Primarily used for adjusting the heatmap based on zoom level. + + The default value of this property is an expression that evaluates to the float + `1`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable + + This property does not support applying interpolation or step functions to + feature attributes. + */ +@property (nonatomic, null_resettable) NSExpression *heatmapIntensity; + +/** + The transition affecting any changes to this layer’s `heatmapIntensity` property. + + This property corresponds to the `heatmap-intensity-transition` property in the style JSON file format. +*/ +@property (nonatomic) MGLTransition heatmapIntensityTransition; + +/** + The global opacity at which the heatmap layer will be drawn. + + The default value of this property is an expression that evaluates to the float + `1`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable + + This property does not support applying interpolation or step functions to + feature attributes. + */ +@property (nonatomic, null_resettable) NSExpression *heatmapOpacity; + +/** + The transition affecting any changes to this layer’s `heatmapOpacity` property. + + This property corresponds to the `heatmap-opacity-transition` property in the style JSON file format. +*/ +@property (nonatomic) MGLTransition heatmapOpacityTransition; + +/** + Radius of influence of one heatmap point in points. Increasing the value makes + the heatmap smoother, but less detailed. + + This property is measured in points. + + The default value of this property is an expression that evaluates to the float + `30`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable and/or + feature attributes + */ +@property (nonatomic, null_resettable) NSExpression *heatmapRadius; + +/** + The transition affecting any changes to this layer’s `heatmapRadius` property. + + This property corresponds to the `heatmap-radius-transition` property in the style JSON file format. +*/ +@property (nonatomic) MGLTransition heatmapRadiusTransition; + +/** + A measure of how much an individual point contributes to the heatmap. A value + of 10 would be equivalent to having 10 points of weight 1 in the same spot. + Especially useful when combined with clustering. + + The default value of this property is an expression that evaluates to the float + `1`. Set this property to `nil` to reset it to the default value. + + You can set this property to an expression containing any of the following: + + * Constant numeric values + * Predefined functions, including mathematical and string operators + * Conditional expressions + * Variable assignments and references to assigned variables + * Interpolation and step functions applied to the `$zoomLevel` variable and/or + feature attributes + */ +@property (nonatomic, null_resettable) NSExpression *heatmapWeight; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLHeatmapStyleLayer.mm b/platform/darwin/src/MGLHeatmapStyleLayer.mm new file mode 100644 index 000000000..a394dbda3 --- /dev/null +++ b/platform/darwin/src/MGLHeatmapStyleLayer.mm @@ -0,0 +1,210 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLSource.h" +#import "NSPredicate+MGLAdditions.h" +#import "NSDate+MGLAdditions.h" +#import "MGLStyleLayer_Private.h" +#import "MGLStyleValue_Private.h" +#import "MGLHeatmapStyleLayer.h" + +#include +#include + +@interface MGLHeatmapStyleLayer () + +@property (nonatomic, readonly) mbgl::style::HeatmapLayer *rawLayer; + +@end + +@implementation MGLHeatmapStyleLayer + +- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source +{ + auto layer = std::make_unique(identifier.UTF8String, source.identifier.UTF8String); + return self = [super initWithPendingLayer:std::move(layer)]; +} + +- (mbgl::style::HeatmapLayer *)rawLayer +{ + return (mbgl::style::HeatmapLayer *)super.rawLayer; +} + +- (NSString *)sourceIdentifier +{ + MGLAssertStyleLayerIsValid(); + + return @(self.rawLayer->getSourceID().c_str()); +} + +- (NSString *)sourceLayerIdentifier +{ + MGLAssertStyleLayerIsValid(); + + auto layerID = self.rawLayer->getSourceLayer(); + return layerID.empty() ? nil : @(layerID.c_str()); +} + +- (void)setSourceLayerIdentifier:(NSString *)sourceLayerIdentifier +{ + MGLAssertStyleLayerIsValid(); + + self.rawLayer->setSourceLayer(sourceLayerIdentifier.UTF8String ?: ""); +} + +- (void)setPredicate:(NSPredicate *)predicate +{ + MGLAssertStyleLayerIsValid(); + + self.rawLayer->setFilter(predicate ? predicate.mgl_filter : mbgl::style::NullFilter()); +} + +- (NSPredicate *)predicate +{ + MGLAssertStyleLayerIsValid(); + + return [NSPredicate mgl_predicateWithFilter:self.rawLayer->getFilter()]; +} + +#pragma mark - Accessing the Paint Attributes + +- (void)setHeatmapColor:(NSExpression *)heatmapColor { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue(heatmapColor); + self.rawLayer->setHeatmapColor(mbglValue); +} + +- (NSExpression *)heatmapColor { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapColor(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapColor(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapIntensity:(NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapIntensity); + self.rawLayer->setHeatmapIntensity(mbglValue); +} + +- (NSExpression *)heatmapIntensity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapIntensity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapIntensity(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapIntensityTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapIntensityTransition(options); +} + +- (MGLTransition)heatmapIntensityTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapIntensityTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapOpacity:(NSExpression *)heatmapOpacity { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapOpacity); + self.rawLayer->setHeatmapOpacity(mbglValue); +} + +- (NSExpression *)heatmapOpacity { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapOpacity(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapOpacity(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapOpacityTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapOpacityTransition(options); +} + +- (MGLTransition)heatmapOpacityTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapOpacityTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapRadius:(NSExpression *)heatmapRadius { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapRadius); + self.rawLayer->setHeatmapRadius(mbglValue); +} + +- (NSExpression *)heatmapRadius { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapRadius(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapRadius(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +- (void)setHeatmapRadiusTransition:(MGLTransition )transition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } }; + self.rawLayer->setHeatmapRadiusTransition(options); +} + +- (MGLTransition)heatmapRadiusTransition { + MGLAssertStyleLayerIsValid(); + + mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHeatmapRadiusTransition(); + MGLTransition transition; + transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero())); + transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero())); + + return transition; +} + +- (void)setHeatmapWeight:(NSExpression *)heatmapWeight { + MGLAssertStyleLayerIsValid(); + + auto mbglValue = MGLStyleValueTransformer().toPropertyValue>(heatmapWeight); + self.rawLayer->setHeatmapWeight(mbglValue); +} + +- (NSExpression *)heatmapWeight { + MGLAssertStyleLayerIsValid(); + + auto propertyValue = self.rawLayer->getHeatmapWeight(); + if (propertyValue.isUndefined()) { + propertyValue = self.rawLayer->getDefaultHeatmapWeight(); + } + return MGLStyleValueTransformer().toExpression(propertyValue); +} + +@end diff --git a/platform/darwin/src/MGLMapSnapshotter.mm b/platform/darwin/src/MGLMapSnapshotter.mm index f03bd70c8..11a544276 100644 --- a/platform/darwin/src/MGLMapSnapshotter.mm +++ b/platform/darwin/src/MGLMapSnapshotter.mm @@ -81,7 +81,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; @end @interface MGLMapSnapshotter() - +@property (nonatomic) BOOL loading; @end @implementation MGLMapSnapshotter { @@ -90,6 +90,7 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; std::unique_ptr _mbglMapSnapshotter; std::unique_ptr> _snapshotCallback; NS_ARRAY_OF(MGLAttributionInfo *) *_attributionInfo; + } - (instancetype)initWithOptions:(MGLMapSnapshotOptions *)options @@ -115,182 +116,192 @@ const CGFloat MGLSnapshotterMinimumPixelSize = 64; format:@"Already started this snapshotter."]; } - _loading = true; + self.loading = true; - dispatch_async(queue, ^{ - _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) { - _loading = false; + __weak __typeof__(self) weakSelf = self; + _snapshotCallback = std::make_unique>(*mbgl::Scheduler::GetCurrent(), [=](std::exception_ptr mbglError, mbgl::PremultipliedImage image, mbgl::MapSnapshotter::Attributions attributions, mbgl::MapSnapshotter::PointForFn pointForFn) { + __typeof__(self) strongSelf = weakSelf; + strongSelf.loading = false; + + + if (mbglError) { + NSString *description = @(mbgl::util::toString(mbglError).c_str()); + NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; + NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo]; - NSMutableArray *infos = [NSMutableArray array]; - + // Dispatch result to origin queue + dispatch_async(queue, ^{ + completion(nil, error); + }); + } else { #if TARGET_OS_IPHONE - CGFloat fontSize = [UIFont smallSystemFontSize]; - UIColor *attributeFontColor = [UIColor blackColor]; + MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:strongSelf.options.scale]; #else - CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize]; - NSColor *attributeFontColor = [NSColor blackColor]; + MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; + mglImage.size = NSMakeSize(mglImage.size.width / strongSelf.options.scale, + mglImage.size.height / strongSelf.options.scale); #endif - for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) { - NSString *attributionHTMLString = @(attribution->c_str()); - NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString - fontSize:fontSize - linkColor:attributeFontColor]; - [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; - } - - _attributionInfo = infos; - - if (mbglError) { - NSString *description = @(mbgl::util::toString(mbglError).c_str()); - NSDictionary *userInfo = @{NSLocalizedDescriptionKey: description}; - NSError *error = [NSError errorWithDomain:MGLErrorDomain code:MGLErrorCodeSnapshotFailed userInfo:userInfo]; - - // Dispatch result to origin queue - dispatch_async(queue, ^{ - completion(nil, error); - }); - } else { -#if TARGET_OS_IPHONE - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image) scale:self.options.scale]; -#else - MGLImage *mglImage = [[MGLImage alloc] initWithMGLPremultipliedImage:std::move(image)]; - mglImage.size = NSMakeSize(mglImage.size.width / self.options.scale, - mglImage.size.height / self.options.scale); -#endif - - // Process image watermark in a work queue - dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - dispatch_async(workQueue, ^{ -#if TARGET_OS_IPHONE - MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; - for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { - attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; - CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; - if (attributionSize.width <= mglImage.size.width) { - break; - } - } - - UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; - CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; - - CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); - if (!logoImage) { - CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; - logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height); - attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); - } - - CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, - attributionOrigin.y, - attributionBackgroundSize.width, - attributionBackgroundSize.height); - CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - attributionBackgroundFrame.origin.y - 1); - - CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, - attributionBackgroundFrame.origin.y * mglImage.scale, - attributionBackgroundSize.width * mglImage.scale, - attributionBackgroundSize.height * mglImage.scale); - - - UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale); - - [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; - - [logoImage drawInRect:logoImageRect]; - - UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext(); - CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect); - UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef]; - CGImageRelease(attributionImageRef); - - CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage]; - - UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage]; - - [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - - [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; - - UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); -#else - NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height); - NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height); - - MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; - for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { - attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; - CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; - if (attributionSize.width <= mglImage.size.width) { - break; - } - } - - NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; - CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; - NSImage *sourceImage = mglImage; - - CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height); - CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, - MGLLogoImagePosition.y + 1); - if (!logoImage) { - CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; - logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height); - attributionOrigin = CGPointMake(10, attributionOrigin.y); - } - - CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, - attributionOrigin.y, - attributionBackgroundSize.width, - attributionBackgroundSize.height); - CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, - logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); - - - NSImage *compositedImage = nil; - NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame - context:nil - hints:nil]; - compositedImage = [[NSImage alloc] initWithSize:targetSize]; - - [compositedImage lockFocus]; - - [sourceImageRep drawInRect: targetFrame]; - - if (logoImage) { - [logoImage drawInRect:logoImageRect]; - } - - NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; - - CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]]; - - NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage]; - - [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; - - [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; - - [compositedImage unlockFocus]; - - -#endif - - // Dispatch result to origin queue - dispatch_async(queue, ^{ - MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn]; - completion(snapshot, nil); - }); - }); - } - }); - _mbglMapSnapshotter->snapshot(_snapshotCallback->self()); + [strongSelf drawAttributedSnapshot:attributions snapshotImage:mglImage pointForFn:pointForFn queue:queue completionHandler:completion]; + } + _snapshotCallback = NULL; }); + dispatch_async(queue, ^{ + _mbglMapSnapshotter->snapshot(_snapshotCallback->self()); + + }); +} + +- (MGLImage *)drawAttributedSnapshot:(mbgl::MapSnapshotter::Attributions)attributions snapshotImage:(MGLImage *)mglImage pointForFn:(mbgl::MapSnapshotter::PointForFn)pointForFn queue:(dispatch_queue_t)queue completionHandler:(MGLMapSnapshotCompletionHandler)completion { + + NSMutableArray *infos = [NSMutableArray array]; + +#if TARGET_OS_IPHONE + CGFloat fontSize = [UIFont smallSystemFontSize]; + UIColor *attributeFontColor = [UIColor blackColor]; +#else + CGFloat fontSize = [NSFont systemFontSizeForControlSize:NSMiniControlSize]; + NSColor *attributeFontColor = [NSColor blackColor]; +#endif + for (auto attribution = attributions.begin(); attribution != attributions.end(); ++attribution) { + NSString *attributionHTMLString = @(attribution->c_str()); + NSArray *tileSetInfos = [MGLAttributionInfo attributionInfosFromHTMLString:attributionHTMLString + fontSize:fontSize + linkColor:attributeFontColor]; + [infos growArrayByAddingAttributionInfosFromArray:tileSetInfos]; + } + + _attributionInfo = infos; + + // Process image watermark in a work queue + dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + dispatch_async(workQueue, ^{ +#if TARGET_OS_IPHONE + MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; + for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; + CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; + if (attributionSize.width <= mglImage.size.width) { + break; + } + } + + UIImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; + + CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, mglImage.size.height - (MGLLogoImagePosition.y + logoImage.size.height), logoImage.size.width, logoImage.size.height); + CGPoint attributionOrigin = CGPointMake(mglImage.size.width - 10 - attributionBackgroundSize.width, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); + if (!logoImage) { + CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; + logoImageRect = CGRectMake(0, mglImage.size.height - (MGLLogoImagePosition.y + defaultLogoSize.height), 0, defaultLogoSize.height); + attributionOrigin = CGPointMake(10, logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2) + 1); + } + + CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, + attributionOrigin.y, + attributionBackgroundSize.width, + attributionBackgroundSize.height); + CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, + attributionBackgroundFrame.origin.y - 1); + + CGRect cropRect = CGRectMake(attributionBackgroundFrame.origin.x * mglImage.scale, + attributionBackgroundFrame.origin.y * mglImage.scale, + attributionBackgroundSize.width * mglImage.scale, + attributionBackgroundSize.height * mglImage.scale); + + + UIGraphicsBeginImageContextWithOptions(mglImage.size, NO, self.options.scale); + + [mglImage drawInRect:CGRectMake(0, 0, mglImage.size.width, mglImage.size.height)]; + + [logoImage drawInRect:logoImageRect]; + + UIImage *currentImage = UIGraphicsGetImageFromCurrentImageContext(); + CGImageRef attributionImageRef = CGImageCreateWithImageInRect([currentImage CGImage], cropRect); + UIImage *attributionImage = [UIImage imageWithCGImage:attributionImageRef]; + CGImageRelease(attributionImageRef); + + CIImage *ciAttributionImage = [[CIImage alloc] initWithCGImage:attributionImage.CGImage]; + + UIImage *blurredAttributionBackground = [self blurredAttributionBackground:ciAttributionImage]; + + [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; + + [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; + + UIImage *compositedImage = UIGraphicsGetImageFromCurrentImageContext(); + + UIGraphicsEndImageContext(); +#else + NSSize targetSize = NSMakeSize(self.options.size.width, self.options.size.height); + NSRect targetFrame = NSMakeRect(0, 0, targetSize.width, targetSize.height); + + MGLAttributionInfoStyle attributionInfoStyle = MGLAttributionInfoStyleLong; + for (NSUInteger styleValue = MGLAttributionInfoStyleLong; styleValue >= MGLAttributionInfoStyleShort; styleValue--) { + attributionInfoStyle = (MGLAttributionInfoStyle)styleValue; + CGSize attributionSize = [self attributionSizeWithLogoStyle:attributionInfoStyle sourceAttributionStyle:attributionInfoStyle]; + if (attributionSize.width <= mglImage.size.width) { + break; + } + } + + NSImage *logoImage = [self logoImageWithStyle:attributionInfoStyle]; + CGSize attributionBackgroundSize = [self attributionTextSizeWithStyle:attributionInfoStyle]; + NSImage *sourceImage = mglImage; + + CGRect logoImageRect = CGRectMake(MGLLogoImagePosition.x, MGLLogoImagePosition.y, logoImage.size.width, logoImage.size.height); + CGPoint attributionOrigin = CGPointMake(targetFrame.size.width - 10 - attributionBackgroundSize.width, + MGLLogoImagePosition.y + 1); + if (!logoImage) { + CGSize defaultLogoSize = [self mapboxLongStyleLogo].size; + logoImageRect = CGRectMake(0, MGLLogoImagePosition.y, 0, defaultLogoSize.height); + attributionOrigin = CGPointMake(10, attributionOrigin.y); + } + + CGRect attributionBackgroundFrame = CGRectMake(attributionOrigin.x, + attributionOrigin.y, + attributionBackgroundSize.width, + attributionBackgroundSize.height); + CGPoint attributionTextPosition = CGPointMake(attributionBackgroundFrame.origin.x + 10, + logoImageRect.origin.y + (logoImageRect.size.height / 2) - (attributionBackgroundSize.height / 2)); + + + NSImage *compositedImage = nil; + NSImageRep *sourceImageRep = [sourceImage bestRepresentationForRect:targetFrame + context:nil + hints:nil]; + compositedImage = [[NSImage alloc] initWithSize:targetSize]; + + [compositedImage lockFocus]; + + [sourceImageRep drawInRect: targetFrame]; + + if (logoImage) { + [logoImage drawInRect:logoImageRect]; + } + + NSBitmapImageRep *attributionBackground = [[NSBitmapImageRep alloc] initWithFocusedViewRect:attributionBackgroundFrame]; + + CIImage *attributionBackgroundImage = [[CIImage alloc] initWithCGImage:[attributionBackground CGImage]]; + + NSImage *blurredAttributionBackground = [self blurredAttributionBackground:attributionBackgroundImage]; + + [blurredAttributionBackground drawInRect:attributionBackgroundFrame]; + + [self drawAttributionTextWithStyle:attributionInfoStyle origin:attributionTextPosition]; + + [compositedImage unlockFocus]; + + +#endif + + // Dispatch result to origin queue + dispatch_async(queue, ^{ + MGLMapSnapshot* snapshot = [[MGLMapSnapshot alloc] initWithImage:compositedImage scale:self.options.scale pointForFn:pointForFn]; + completion(snapshot, nil); + }); + }); + return nil; } - (void)drawAttributionTextWithStyle:(MGLAttributionInfoStyle)attributionInfoStyle origin:(CGPoint)origin diff --git a/platform/darwin/src/MGLRendererFrontend.h b/platform/darwin/src/MGLRendererFrontend.h index 76904d008..2df67ca4e 100644 --- a/platform/darwin/src/MGLRendererFrontend.h +++ b/platform/darwin/src/MGLRendererFrontend.h @@ -56,9 +56,9 @@ public: return renderer.get(); } - void onLowMemory() { + void reduceMemoryUse() { if (!renderer) return; - renderer->onLowMemory(); + renderer->reduceMemoryUse(); } private: diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 512894431..f6fc5533b 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -8,6 +8,7 @@ #import "MGLLineStyleLayer.h" #import "MGLCircleStyleLayer.h" #import "MGLSymbolStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLRasterStyleLayer.h" #import "MGLBackgroundStyleLayer.h" @@ -36,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -401,6 +403,8 @@ static NSURL *MGLStyleURL_trafficNight; return [[MGLSymbolStyleLayer alloc] initWithRawLayer:symbolLayer]; } else if (auto rasterLayer = rawLayer->as()) { return [[MGLRasterStyleLayer alloc] initWithRawLayer:rasterLayer]; + } else if (auto heatmapLayer = rawLayer->as()) { + return [[MGLHeatmapStyleLayer alloc] initWithRawLayer:heatmapLayer]; } else if (auto hillshadeLayer = rawLayer->as()) { return [[MGLHillshadeStyleLayer alloc] initWithRawLayer:hillshadeLayer]; } else if (auto circleLayer = rawLayer->as()) { diff --git a/platform/darwin/src/MGLStyleLayer.mm.ejs b/platform/darwin/src/MGLStyleLayer.mm.ejs index 41b029791..ac7676a1c 100644 --- a/platform/darwin/src/MGLStyleLayer.mm.ejs +++ b/platform/darwin/src/MGLStyleLayer.mm.ejs @@ -157,7 +157,9 @@ namespace mbgl { - (void)set<%- camelize(property.name) %>:(NSExpression *)<%- objCName(property) %> { MGLAssertStyleLayerIsValid(); -<% if (property["property-function"]) { -%> +<% if (property.name === 'heatmap-color') { -%> + auto mbglValue = MGLStyleValueTransformer().toPropertyValue(heatmapColor); +<% } else if (property["property-function"]) { -%> auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue>>(<%- objCName(property) %>); <% } else { -%> auto mbglValue = MGLStyleValueTransformer<<%- valueTransformerArguments(property).join(', ') %>>().toPropertyValue>>(<%- objCName(property) %>); diff --git a/platform/darwin/src/MGLStyleValue_Private.h b/platform/darwin/src/MGLStyleValue_Private.h index ba4b413a3..5124c29a9 100644 --- a/platform/darwin/src/MGLStyleValue_Private.h +++ b/platform/darwin/src/MGLStyleValue_Private.h @@ -10,6 +10,7 @@ #import "MGLConversion.h" #include #include +#include #include #import @@ -52,11 +53,20 @@ public: return mbglValue.evaluate(evaluator); } + // Convert an mbgl heatmap color property value into an mgl style value + NSExpression *toExpression(const mbgl::style::HeatmapColorPropertyValue &mbglValue) { + if (mbglValue.isUndefined()) { + return nil; + } + return [NSExpression mgl_expressionWithJSONObject:MGLJSONObjectFromMBGLExpression(mbglValue.getExpression())]; + } + /** Converts an NSExpression to an mbgl property value. */ template - MBGLValue toPropertyValue(NSExpression *expression) { + typename std::enable_if_t::value, + MBGLValue> toPropertyValue(NSExpression *expression) { if (!expression) { return {}; } @@ -85,6 +95,30 @@ public: return *value; } + + /** + Converts an NSExpression to an mbgl property value. + */ + template + typename std::enable_if_t::value, + MBGLValue> toPropertyValue(NSExpression *expression) { + if (!expression) { + return {}; + } + + NSArray *jsonExpression = expression.mgl_jsonExpressionObject; + + mbgl::style::conversion::Error valueError; + auto value = mbgl::style::conversion::convert( + mbgl::style::conversion::makeConvertible(jsonExpression), valueError); + if (!value) { + [NSException raise:NSInvalidArgumentException + format:@"Invalid property value: %@", @(valueError.message.c_str())]; + return {}; + } + + return *value; + } private: // Private utilities for converting from mgl to mbgl values diff --git a/platform/darwin/src/MGLTileSource.h b/platform/darwin/src/MGLTileSource.h index caeafcd2f..3dc268db6 100644 --- a/platform/darwin/src/MGLTileSource.h +++ b/platform/darwin/src/MGLTileSource.h @@ -41,6 +41,20 @@ extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel; */ extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel; +/** + An `NSValue` object containing an `MGLCoordinateBounds` struct that specifies + the geographic extent of the source. + + If this option is specified, the SDK avoids requesting any tile that falls + outside of the coordinate bounds. Otherwise, the SDK requests any tile needed + to cover the viewport, as it does by default. + + This option corresponds to the `bounds` key in the + TileJSON + specification. + */ +extern MGL_EXPORT const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds; + #if TARGET_OS_IPHONE /** An HTML string defining the buttons to be displayed in an action sheet when the diff --git a/platform/darwin/src/MGLTileSource.mm b/platform/darwin/src/MGLTileSource.mm index 5644ad9a0..bc985bd72 100644 --- a/platform/darwin/src/MGLTileSource.mm +++ b/platform/darwin/src/MGLTileSource.mm @@ -1,7 +1,9 @@ #import "MGLTileSource_Private.h" #import "MGLAttributionInfo_Private.h" +#import "MGLGeometry_Private.h" #import "NSString+MGLAdditions.h" +#import "NSValue+MGLAdditions.h" #if TARGET_OS_IPHONE #import @@ -13,6 +15,7 @@ const MGLTileSourceOption MGLTileSourceOptionMinimumZoomLevel = @"MGLTileSourceOptionMinimumZoomLevel"; const MGLTileSourceOption MGLTileSourceOptionMaximumZoomLevel = @"MGLTileSourceOptionMaximumZoomLevel"; +const MGLTileSourceOption MGLTileSourceOptionCoordinateBounds = @"MGLTileSourceOptionCoordinateBounds"; const MGLTileSourceOption MGLTileSourceOptionAttributionHTMLString = @"MGLTileSourceOptionAttributionHTMLString"; const MGLTileSourceOption MGLTileSourceOptionAttributionInfos = @"MGLTileSourceOptionAttributionInfos"; const MGLTileSourceOption MGLTileSourceOptionTileCoordinateSystem = @"MGLTileSourceOptionTileCoordinateSystem"; @@ -70,6 +73,15 @@ mbgl::Tileset MGLTileSetFromTileURLTemplates(NS_ARRAY_OF(NSString *) *tileURLTem [NSException raise:NSInvalidArgumentException format:@"MGLTileSourceOptionMinimumZoomLevel must be less than MGLTileSourceOptionMaximumZoomLevel."]; } + + if (NSValue *coordinateBounds = options[MGLTileSourceOptionCoordinateBounds]) { + if (![coordinateBounds isKindOfClass:[NSValue class]] + && strcmp(coordinateBounds.objCType, @encode(MGLCoordinateBounds)) == 0) { + [NSException raise:NSInvalidArgumentException + format:@"MGLTileSourceOptionCoordinateBounds must be set to an NSValue containing an MGLCoordinateBounds."]; + } + tileSet.bounds = MGLLatLngBoundsFromCoordinateBounds(coordinateBounds.MGLCoordinateBoundsValue); + } if (NSString *attribution = options[MGLTileSourceOptionAttributionHTMLString]) { if (![attribution isKindOfClass:[NSString class]]) { diff --git a/platform/darwin/src/MGLVectorStyleLayer.h b/platform/darwin/src/MGLVectorStyleLayer.h index 7780a34c7..177b1b70f 100644 --- a/platform/darwin/src/MGLVectorStyleLayer.h +++ b/platform/darwin/src/MGLVectorStyleLayer.h @@ -10,10 +10,10 @@ NS_ASSUME_NONNULL_BEGIN is defined by an `MGLShapeSource` or `MGLVectorSource` object. Create instances of `MGLCircleStyleLayer`, `MGLFillStyleLayer`, - `MGLFillExtrusionStyleLayer`, `MGLLineStyleLayer`, and `MGLSymbolStyleLayer` in - order to use `MGLVectorStyleLayer`'s properties and methods. Do not create - instances of `MGLVectorStyleLayer` directly, and do not create your own - subclasses of this class. + `MGLFillExtrusionStyleLayer`, `MGLHeatmapStyleLayer`, `MGLLineStyleLayer`, and + `MGLSymbolStyleLayer` in order to use `MGLVectorStyleLayer`'s properties and + methods. Do not create instances of `MGLVectorStyleLayer` directly, and do not + create your own subclasses of this class. */ MGL_EXPORT @interface MGLVectorStyleLayer : MGLForegroundStyleLayer diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 9bf992486..5a6e00bc4 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -230,6 +230,24 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.layer(withIdentifier: "buildings")) } + + func testMGLHeatmapStyleLayer() { + let earthquakes = MGLShapeSource(identifier: "earthquakes", url: URL(string: "https://example.com/earthquakes.json")!, options: [:]) + mapView.style?.addSource(earthquakes) + + //#-example-code + let layer = MGLHeatmapStyleLayer(identifier: "earthquake-heat", source: earthquakes) + layer.heatmapWeight = NSExpression(format: "FUNCTION(magnitude, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 0, + 6: 1]) + layer.heatmapIntensity = NSExpression(format: "FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", + [0: 1, + 9: 3]) + mapView.style?.addLayer(layer) + //#-end-example-code + + XCTAssertNotNil(mapView.style?.layer(withIdentifier: "earthquake-heat")) + } func testMGLSymbolStyleLayer() { let pois = MGLVectorSource(identifier: "pois", configurationURL: URL(string: "https://example.com/style.json")!) diff --git a/platform/darwin/test/MGLHeatmapColorTests.mm b/platform/darwin/test/MGLHeatmapColorTests.mm new file mode 100644 index 000000000..8d44064d9 --- /dev/null +++ b/platform/darwin/test/MGLHeatmapColorTests.mm @@ -0,0 +1,61 @@ +#import +#import + +#import "MGLStyleLayer_Private.h" + +#include + +@interface MGLHeatmapColorTests : XCTestCase +@end + +@implementation MGLHeatmapColorTests + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + auto rawLayer = layer.rawLayer->as(); + + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"heatmap-color should be unset initially."); + NSExpression *defaultExpression = layer.heatmapColor; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + layer.heatmapColor = constantExpression; + + + mbgl::style::PropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(0.0), mbgl::Color::red(), + @"Setting heatmapColor to a constant value expression should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, constantExpression, + @"heatmapColor should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"%@", [MGLColor redColor]]; + NSExpression *constantExpression2 = [NSExpression expressionWithFormat:@"%@", [MGLColor blueColor]]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($heatmapDensity, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@12: constantExpression2}]; + layer.heatmapColor = functionExpression; + + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(11.0), mbgl::Color::red(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqual(rawLayer->getHeatmapColor().evaluate(12.0), mbgl::Color::blue(), + @"Setting heatmapColor to an expression depending on $heatmapDensity should update heatmap-color."); + XCTAssertEqualObjects(layer.heatmapColor, functionExpression, + @"heatmapColor should round-trip expressions depending on $heatmapDensity."); + + layer.heatmapColor = nil; + XCTAssertTrue(rawLayer->getHeatmapColor().isUndefined(), + @"Unsetting heatmapColor should return heatmap-color to the default value."); + XCTAssertEqualObjects(layer.heatmapColor, defaultExpression, + @"heatmapColor should return the default value after being unset."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a data expression is applied to heatmapColor."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapColor = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); +} + +@end diff --git a/platform/darwin/test/MGLHeatmapStyleLayerTests.mm b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm new file mode 100644 index 000000000..74121affd --- /dev/null +++ b/platform/darwin/test/MGLHeatmapStyleLayerTests.mm @@ -0,0 +1,296 @@ +// This file is generated. +// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`. + +#import "MGLStyleLayerTests.h" +#import "../../darwin/src/NSDate+MGLAdditions.h" + +#import "MGLStyleLayer_Private.h" + +#include +#include + +@interface MGLHeatmapLayerTests : MGLStyleLayerTests +@end + +@implementation MGLHeatmapLayerTests + ++ (NSString *)layerType { + return @"heatmap"; +} + +- (void)testPredicates { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + + XCTAssertNil(layer.sourceLayerIdentifier); + layer.sourceLayerIdentifier = @"layerID"; + XCTAssertEqualObjects(layer.sourceLayerIdentifier, @"layerID"); + layer.sourceLayerIdentifier = nil; + XCTAssertNil(layer.sourceLayerIdentifier); + + XCTAssertNil(layer.predicate); + layer.predicate = [NSPredicate predicateWithValue:NO]; + XCTAssertEqualObjects(layer.predicate, [NSPredicate predicateWithValue:NO]); + layer.predicate = nil; + XCTAssertNil(layer.predicate); +} + +- (void)testProperties { + MGLPointFeature *feature = [[MGLPointFeature alloc] init]; + MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; + + MGLHeatmapStyleLayer *layer = [[MGLHeatmapStyleLayer alloc] initWithIdentifier:@"layerID" source:source]; + XCTAssertNotEqual(layer.rawLayer, nullptr); + XCTAssertTrue(layer.rawLayer->is()); + auto rawLayer = layer.rawLayer->as(); + + MGLTransition transitionTest = MGLTransitionMake(5, 4); + + + // heatmap-intensity + { + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"heatmap-intensity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapIntensity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapIntensity = constantExpression; + mbgl::style::PropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a constant value expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, constantExpression, + @"heatmapIntensity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapIntensity = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapIntensity(), propertyValue, + @"Setting heatmapIntensity to a camera expression should update heatmap-intensity."); + XCTAssertEqualObjects(layer.heatmapIntensity, functionExpression, + @"heatmapIntensity should round-trip camera expressions."); + + + + layer.heatmapIntensity = nil; + XCTAssertTrue(rawLayer->getHeatmapIntensity().isUndefined(), + @"Unsetting heatmapIntensity should return heatmap-intensity to the default value."); + XCTAssertEqualObjects(layer.heatmapIntensity, defaultExpression, + @"heatmapIntensity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapIntensity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapIntensityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapIntensityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapIntensityTransition = layer.heatmapIntensityTransition; + XCTAssertEqual(heatmapIntensityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapIntensityTransition.duration, transitionTest.duration); + } + + // heatmap-opacity + { + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"heatmap-opacity should be unset initially."); + NSExpression *defaultExpression = layer.heatmapOpacity; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapOpacity = constantExpression; + mbgl::style::PropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a constant value expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, constantExpression, + @"heatmapOpacity should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapOpacity = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapOpacity(), propertyValue, + @"Setting heatmapOpacity to a camera expression should update heatmap-opacity."); + XCTAssertEqualObjects(layer.heatmapOpacity, functionExpression, + @"heatmapOpacity should round-trip camera expressions."); + + + + layer.heatmapOpacity = nil; + XCTAssertTrue(rawLayer->getHeatmapOpacity().isUndefined(), + @"Unsetting heatmapOpacity should return heatmap-opacity to the default value."); + XCTAssertEqualObjects(layer.heatmapOpacity, defaultExpression, + @"heatmapOpacity should return the default value after being unset."); + + functionExpression = [NSExpression expressionForKeyPath:@"bogus"]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(bogus, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + XCTAssertThrowsSpecificNamed(layer.heatmapOpacity = functionExpression, NSException, NSInvalidArgumentException, @"MGLHeatmapLayer should raise an exception if a camera-data expression is applied to a property that does not support key paths to feature attributes."); + // Transition property test + layer.heatmapOpacityTransition = transitionTest; + auto toptions = rawLayer->getHeatmapOpacityTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapOpacityTransition = layer.heatmapOpacityTransition; + XCTAssertEqual(heatmapOpacityTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapOpacityTransition.duration, transitionTest.duration); + } + + // heatmap-radius + { + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"heatmap-radius should be unset initially."); + NSExpression *defaultExpression = layer.heatmapRadius; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapRadius = constantExpression; + mbgl::style::DataDrivenPropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a constant value expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, constantExpression, + @"heatmapRadius should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapRadius = functionExpression; + + mbgl::style::ExponentialStops exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a data expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapRadius = functionExpression; + + std::map innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapRadius(), propertyValue, + @"Setting heatmapRadius to a camera-data expression should update heatmap-radius."); + XCTAssertEqualObjects(layer.heatmapRadius, functionExpression, + @"heatmapRadius should round-trip camera-data expressions."); + + + layer.heatmapRadius = nil; + XCTAssertTrue(rawLayer->getHeatmapRadius().isUndefined(), + @"Unsetting heatmapRadius should return heatmap-radius to the default value."); + XCTAssertEqualObjects(layer.heatmapRadius, defaultExpression, + @"heatmapRadius should return the default value after being unset."); + // Transition property test + layer.heatmapRadiusTransition = transitionTest; + auto toptions = rawLayer->getHeatmapRadiusTransition(); + XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay); + XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration); + + MGLTransition heatmapRadiusTransition = layer.heatmapRadiusTransition; + XCTAssertEqual(heatmapRadiusTransition.delay, transitionTest.delay); + XCTAssertEqual(heatmapRadiusTransition.duration, transitionTest.duration); + } + + // heatmap-weight + { + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"heatmap-weight should be unset initially."); + NSExpression *defaultExpression = layer.heatmapWeight; + + NSExpression *constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + layer.heatmapWeight = constantExpression; + mbgl::style::DataDrivenPropertyValue propertyValue = { 0xff }; + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a constant value expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, constantExpression, + @"heatmapWeight should round-trip constant value expressions."); + + constantExpression = [NSExpression expressionWithFormat:@"0xff"]; + NSExpression *functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_stepWithMinimum:stops:', %@, %@)", constantExpression, @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::IntervalStops intervalStops = {{ + { -INFINITY, 0xff }, + { 18, 0xff }, + }}; + propertyValue = mbgl::style::CameraFunction { intervalStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip camera expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION(keyName, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@18: constantExpression}]; + layer.heatmapWeight = functionExpression; + + mbgl::style::ExponentialStops exponentialStops = { {{18, 0xff}}, 1.0 }; + propertyValue = mbgl::style::SourceFunction { "keyName", exponentialStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a data expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip data expressions."); + + functionExpression = [NSExpression expressionWithFormat:@"FUNCTION($zoomLevel, 'mgl_interpolateWithCurveType:parameters:stops:', 'linear', nil, %@)", @{@10: functionExpression}]; + layer.heatmapWeight = functionExpression; + + std::map innerStops { {18, 0xff} }; + mbgl::style::CompositeExponentialStops compositeStops { { {10.0, innerStops} }, 1.0 }; + + propertyValue = mbgl::style::CompositeFunction { "keyName", compositeStops }; + + XCTAssertEqual(rawLayer->getHeatmapWeight(), propertyValue, + @"Setting heatmapWeight to a camera-data expression should update heatmap-weight."); + XCTAssertEqualObjects(layer.heatmapWeight, functionExpression, + @"heatmapWeight should round-trip camera-data expressions."); + + + layer.heatmapWeight = nil; + XCTAssertTrue(rawLayer->getHeatmapWeight().isUndefined(), + @"Unsetting heatmapWeight should return heatmap-weight to the default value."); + XCTAssertEqualObjects(layer.heatmapWeight, defaultExpression, + @"heatmapWeight should return the default value after being unset."); + } +} + +- (void)testPropertyNames { + [self testPropertyName:@"heatmap-intensity" isBoolean:NO]; + [self testPropertyName:@"heatmap-opacity" isBoolean:NO]; + [self testPropertyName:@"heatmap-radius" isBoolean:NO]; + [self testPropertyName:@"heatmap-weight" isBoolean:NO]; +} + +@end diff --git a/platform/darwin/test/MGLStyleLayerTests.mm.ejs b/platform/darwin/test/MGLStyleLayerTests.mm.ejs index e26c63e66..e17501ed1 100644 --- a/platform/darwin/test/MGLStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MGLStyleLayerTests.mm.ejs @@ -59,6 +59,7 @@ MGLTransition transitionTest = MGLTransitionMake(5, 4); <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> // <%- originalPropertyName(property) %> { @@ -151,6 +152,7 @@ - (void)testPropertyNames { <% for (const property of properties) { -%> +<% if (property.name === 'heatmap-color') continue; -%> [self testPropertyName:@"<%- property.getter || property.name %>" isBoolean:<%- property.type === 'boolean' ? 'YES' : 'NO' %>]; <% } -%> } diff --git a/platform/darwin/test/MGLTileSetTests.mm b/platform/darwin/test/MGLTileSetTests.mm index 40eab5f97..4d5e1fcd0 100644 --- a/platform/darwin/test/MGLTileSetTests.mm +++ b/platform/darwin/test/MGLTileSetTests.mm @@ -2,6 +2,7 @@ #import #import "MGLTileSource_Private.h" +#import "MGLGeometry_Private.h" #include @@ -40,6 +41,19 @@ XCTAssertEqual(tileSet.zoomRange.min, 1); XCTAssertEqual(tileSet.zoomRange.max, 2); + // when the tile set has a bounds set + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(12, 34), CLLocationCoordinate2DMake(56, 78)); + tileSet = MGLTileSetFromTileURLTemplates(@[@"tile.1"], @{ + MGLTileSourceOptionCoordinateBounds: @(bounds), + }); + + // the mbgl object reflects the set values for the bounds + XCTAssert(!!tileSet.bounds, @"The bounds are set after setting the bounds"); + if (tileSet.bounds) { + MGLCoordinateBounds actual = MGLCoordinateBoundsFromLatLngBounds(*tileSet.bounds); + XCTAssert(MGLCoordinateBoundsEqualToCoordinateBounds(bounds, actual), @"The bounds round-trip"); + } + // when the tile set has an attribution NSString *attribution = @"my tileset © ©️🎈"; tileSet = MGLTileSetFromTileURLTemplates(tileURLTemplates, @{ diff --git a/platform/default/asset_file_source.cpp b/platform/default/asset_file_source.cpp index 54dbb8d0f..3063bf88a 100644 --- a/platform/default/asset_file_source.cpp +++ b/platform/default/asset_file_source.cpp @@ -10,6 +10,12 @@ #include #include +namespace { + +const std::string assetProtocol = "asset://"; + +} // namespace + namespace mbgl { class AssetFileSource::Impl { @@ -19,18 +25,17 @@ public: } void request(const std::string& url, ActorRef req) { - std::string path; - - if (url.size() <= 8 || url[8] == '/') { - // This is an empty or absolute path. - path = mbgl::util::percentDecode(url.substr(8)); - } else { - // This is a relative path. Prefix with the application root. - path = root + "/" + mbgl::util::percentDecode(url.substr(8)); - } - Response response; + if (!acceptsURL(url)) { + response.error = std::make_unique(Response::Error::Reason::Other, + "Invalid asset URL"); + req.invoke(&FileSourceRequest::setResponse, response); + return; + } + + // Cut off the protocol and prefix with path. + const auto path = root + "/" + mbgl::util::percentDecode(url.substr(assetProtocol.size())); struct stat buf; int result = stat(path.c_str(), &buf); @@ -69,4 +74,8 @@ std::unique_ptr AssetFileSource::request(const Resource& resource, return std::move(req); } +bool AssetFileSource::acceptsURL(const std::string& url) { + return std::equal(assetProtocol.begin(), assetProtocol.end(), url.begin()); +} + } // namespace mbgl diff --git a/platform/default/default_file_source.cpp b/platform/default/default_file_source.cpp index 608b782ab..cb602995a 100644 --- a/platform/default/default_file_source.cpp +++ b/platform/default/default_file_source.cpp @@ -14,16 +14,6 @@ #include -namespace { - -const std::string assetProtocol = "asset://"; - -bool isAssetURL(const std::string& url) { - return std::equal(assetProtocol.begin(), assetProtocol.end(), url.begin()); -} - -} // namespace - namespace mbgl { class DefaultFileSource::Impl { @@ -118,7 +108,7 @@ public: ref.invoke(&FileSourceRequest::setResponse, res); }; - if (isAssetURL(resource.url)) { + if (AssetFileSource::acceptsURL(resource.url)) { //Asset request tasks[req] = assetFileSource->request(resource, callback); } else if (LocalFileSource::acceptsURL(resource.url)) { diff --git a/platform/default/local_file_source.cpp b/platform/default/local_file_source.cpp index 21803d093..0635e86d8 100644 --- a/platform/default/local_file_source.cpp +++ b/platform/default/local_file_source.cpp @@ -16,8 +16,7 @@ namespace { -const char* protocol = "file://"; -const std::size_t protocolLength = 7; +const std::string fileProtocol = "file://"; } // namespace @@ -28,11 +27,17 @@ public: Impl(ActorRef) {} void request(const std::string& url, ActorRef req) { - // Cut off the protocol - std::string path = mbgl::util::percentDecode(url.substr(protocolLength)); - Response response; + if (!acceptsURL(url)) { + response.error = std::make_unique(Response::Error::Reason::Other, + "Invalid file URL"); + req.invoke(&FileSourceRequest::setResponse, response); + return; + } + + // Cut off the protocol and prefix with path. + const auto path = mbgl::util::percentDecode(url.substr(fileProtocol.size())); struct stat buf; int result = stat(path.c_str(), &buf); @@ -70,7 +75,7 @@ std::unique_ptr LocalFileSource::request(const Resource& resource, } bool LocalFileSource::acceptsURL(const std::string& url) { - return url.compare(0, protocolLength, protocol) == 0; + return std::equal(fileProtocol.begin(), fileProtocol.end(), url.begin()); } } // namespace mbgl diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 0cb39024a..75a732117 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -11,6 +11,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Styles and rendering +* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046) * The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726)) * Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983)) * A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642)) @@ -34,15 +35,19 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Map snapshots * Fixed a memory leak that occurred when creating a map snapshot. ([#10585](https://github.com/mapbox/mapbox-gl-native/pull/10585)) -* Fixed an issue that caused `MGLMapSnapshotter.pointForCoordinate` to return an incorrect value. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035)) -### Other changes +## Other changes * Feature querying results now account for the `MGLSymbolStyleLayer.circleStrokeWidth` property. ([#10897](https://github.com/mapbox/mapbox-gl-native/pull/10897)) * Fixed an issue preventing labels from being transliterated when VoiceOver was enabled on iOS 10._x_ and below. ([#10881](https://github.com/mapbox/mapbox-gl-native/pull/10881)) * Labels are now transliterated from more languages when VoiceOver is enabled. ([#10881](https://github.com/mapbox/mapbox-gl-native/pull/10881)) * Long-pressing the attribution button causes the SDK’s version number to be displayed in the action sheet that appears. ([#10650](https://github.com/mapbox/mapbox-gl-native/pull/10650)) +## 3.7.4 - February 12, 2018 + +* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141)) +* Fixed an issue that caused `-[MGLMapSnapshotter pointForCoordinate:]` to return the wrong point. ([#11035](https://github.com/mapbox/mapbox-gl-native/pull/11035)) + ## 3.7.3 - January 10, 2018 * Fixed a crash while zooming while annotations are present on the map. ([#10791](https://github.com/mapbox/mapbox-gl-native/pull/10791)) diff --git a/platform/ios/app/MBXSnapshotsViewController.m b/platform/ios/app/MBXSnapshotsViewController.m index 3bf93d872..95d3251e2 100644 --- a/platform/ios/app/MBXSnapshotsViewController.m +++ b/platform/ios/app/MBXSnapshotsViewController.m @@ -50,12 +50,13 @@ options.zoomLevel = 10; // Create and start the snapshotter + __weak UIImageView *weakImageView = imageView; MGLMapSnapshotter* snapshotter = [[MGLMapSnapshotter alloc] initWithOptions:options]; [snapshotter startWithCompletionHandler: ^(MGLMapSnapshot* snapshot, NSError *error) { if (error) { NSLog(@"Could not load snapshot: %@", [error localizedDescription]); } else { - imageView.image = snapshot.image; + weakImageView.image = snapshot.image; } }]; diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 0f617188b..282dc901d 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -1280,6 +1280,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { - (void)styleDynamicPointCollection { [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(36.9979, -109.0441) zoomLevel:14 animated:NO]; + CLLocationCoordinate2D coordinates[] = { {37.00145594210082, -109.04960632324219}, {37.00173012609867, -109.0404224395752}, @@ -1889,7 +1890,8 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { [self updateHUD]; } -- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { +- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated +{ [self updateHUD]; } diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md index 8db1bd331..02d23067e 100644 --- a/platform/ios/docs/guides/For Style Authors.md +++ b/platform/ios/docs/guides/For Style Authors.md @@ -109,6 +109,7 @@ the following terms for concepts defined in the style specification: In the style specification | In the SDK ---------------------------|--------- +bounds | coordinate bounds filter | predicate function type | interpolation mode id | identifier @@ -149,6 +150,7 @@ In style JSON | In TileJSON | In the SDK `tiles` | `tiles` | `tileURLTemplates` parameter in `-[MGLTileSource initWithIdentifier:tileURLTemplates:options:]` `minzoom` | `minzoom` | `MGLTileSourceOptionMinimumZoomLevel` `maxzoom` | `maxzoom` | `MGLTileSourceOptionMaximumZoomLevel` +`bounds` | `bounds` | `MGLTileSourceOptionCoordinateBounds` `tileSize` | — | `MGLTileSourceOptionTileSize` `attribution` | `attribution` | `MGLTileSourceOptionAttributionHTMLString` (but consider specifying `MGLTileSourceOptionAttributionInfos` instead for improved security) `scheme` | `scheme` | `MGLTileSourceOptionTileCoordinateSystem` @@ -190,6 +192,7 @@ In style JSON | In the SDK `circle` | `MGLCircleStyleLayer` `fill` | `MGLFillStyleLayer` `fill-extrusion` | `MGLFillExtrusionStyleLayer` +`heatmap` | `MGLHeatmapStyleLayer` `hillshade` | `MGLHillshadeStyleLayer` `line` | `MGLLineStyleLayer` `raster` | `MGLRasterStyleLayer` diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 68071123a..f66aa7496 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -28,6 +28,8 @@ 16376B471FFDB92B0000563E /* one-liner.json in Resources */ = {isa = PBXBuildFile; fileRef = DA35D0871E1A6309007DED41 /* one-liner.json */; }; 16376B491FFEED010000563E /* MGLMapViewLayoutTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */; }; 165D0CE720005419009A3C66 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; }; + 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */; }; + 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */; }; 1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -226,6 +228,10 @@ 55E2AD131E5B125400E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD121E5B125400E8C587 /* MGLOfflineStorageTests.mm */; }; 632281DF1E6F855900D75A5D /* MBXEmbeddedMapViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */; }; 6407D6701E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */; }; + 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; }; + 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */; }; 920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */; }; 927FBCFC1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */; }; 927FBCFF1F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 927FBCFD1F4DB05500F8BF1F /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -297,6 +303,12 @@ 96E5170320005A6800A02306 /* FABKitProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848811CBB033F00AB86E3 /* FABKitProtocol.h */; }; 96E5170420005A6B00A02306 /* SMCalloutView.h in Headers */ = {isa = PBXBuildFile; fileRef = DA8848891CBB037E00AB86E3 /* SMCalloutView.h */; }; 96F3F73C1F57124B003E2D2C /* MGLUserLocationHeadingIndicator.h in Headers */ = {isa = PBXBuildFile; fileRef = 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */; }; + AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; + AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */; }; + AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; + AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; + CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC8F1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; DA00FC901D5EEB0D009AABC8 /* MGLAttributionInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */; }; @@ -666,6 +678,8 @@ 16376B3F1FFDB4B40000563E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 16376B401FFDB4B40000563E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 16376B481FFEED010000563E /* MGLMapViewLayoutTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLMapViewLayoutTests.m; sourceTree = ""; }; + 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapColorTests.mm; path = ../../darwin/test/MGLHeatmapColorTests.mm; sourceTree = ""; }; + 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLHeatmapStyleLayerTests.mm; path = ../../darwin/test/MGLHeatmapStyleLayerTests.mm; sourceTree = ""; }; 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; 1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = ""; }; @@ -797,6 +811,8 @@ 632281DD1E6F855900D75A5D /* MBXEmbeddedMapViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXEmbeddedMapViewController.h; sourceTree = ""; }; 632281DE1E6F855900D75A5D /* MBXEmbeddedMapViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXEmbeddedMapViewController.m; sourceTree = ""; }; 6407D66F1E0085FD00F6A9C3 /* MGLDocumentationExampleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MGLDocumentationExampleTests.swift; path = ../../darwin/test/MGLDocumentationExampleTests.swift; sourceTree = ""; }; + 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = ""; }; + 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = ""; }; 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLSourceQueryTests.m; path = ../../darwin/test/MGLSourceQueryTests.m; sourceTree = ""; }; 927FBCFA1F4DAA8300F8BF1F /* MBXSnapshotsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MBXSnapshotsViewController.h; sourceTree = ""; }; 927FBCFB1F4DAA8300F8BF1F /* MBXSnapshotsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MBXSnapshotsViewController.m; sourceTree = ""; }; @@ -831,6 +847,9 @@ 96E0272D1E57C7E6004B8E66 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; 96E0272E1E57C7E7004B8E66 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; 96F3F73B1F5711F1003E2D2C /* MGLUserLocationHeadingIndicator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLUserLocationHeadingIndicator.h; sourceTree = ""; }; + AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = ""; }; + AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = ""; }; + CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; sourceTree = ""; }; DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = ""; }; DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = ""; }; DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = ""; }; @@ -1236,6 +1255,8 @@ FA68F1481E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.h */, FA68F1491E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.mm */, 35D13AC11D3D19DD00AFB4E0 /* MGLFillStyleLayer.h */, + 8989B17A201A48EA0081CF59 /* MGLHeatmapStyleLayer.h */, + 8989B17B201A48EA0081CF59 /* MGLHeatmapStyleLayer.mm */, 35D13AC21D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm */, 3538AA1B1D542239008EC33D /* MGLForegroundStyleLayer.h */, 3538AA1C1D542239008EC33D /* MGLForegroundStyleLayer.mm */, @@ -1306,6 +1327,8 @@ 3575798F1D513EF1000B822E /* Layers */ = { isa = PBXGroup; children = ( + 170C43782028D49800863DF0 /* MGLHeatmapColorTests.mm */, + 170C43792028D49800863DF0 /* MGLHeatmapStyleLayerTests.mm */, DA2DBBCC1D51E80400D38FF9 /* MGLStyleLayerTests.h */, DA2DBBCD1D51E80400D38FF9 /* MGLStyleLayerTests.m */, DA3C6FF21E2859E700F962BE /* test-Bridging-Header.h */, @@ -1587,19 +1610,20 @@ DA8848331CBAFB2A00AB86E3 /* Kit */ = { isa = PBXGroup; children = ( - 355ADFF91E9281C300F3939D /* Views */, - 35CE617F1D4165C2004F2359 /* Categories */, DAD165841CF4D06B001FF4B9 /* Annotations */, - DAD165851CF4D08B001FF4B9 /* Telemetry */, - DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */, - DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */, - DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, - DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */, - DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, - DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, - DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, + 35CE617F1D4165C2004F2359 /* Categories */, DA88487F1CBB033F00AB86E3 /* Fabric */, DA8848881CBB036000AB86E3 /* SMCalloutView */, + DAD165851CF4D08B001FF4B9 /* Telemetry */, + 355ADFF91E9281C300F3939D /* Views */, + CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */, + DA704CC01F65A475004B3F28 /* MGLMapAccessibilityElement.h */, + DA704CC11F65A475004B3F28 /* MGLMapAccessibilityElement.mm */, + DA17BE2F1CC4BAC300402C41 /* MGLMapView_Private.h */, + DA8848361CBAFB8500AB86E3 /* MGLMapView.h */, + DA88484A1CBAFB9800AB86E3 /* MGLMapView.mm */, + DA8848371CBAFB8500AB86E3 /* MGLMapView+IBAdditions.h */, + DA737EE01D056A4E005BDA16 /* MGLMapViewDelegate.h */, ); name = Kit; path = src; @@ -1858,6 +1882,8 @@ DA8848491CBAFB9800AB86E3 /* MGLMapboxEvents.m */, 9620BB361E69FE1700705A1D /* MGLSDKUpdateChecker.h */, 9620BB371E69FE1700705A1D /* MGLSDKUpdateChecker.mm */, + AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */, + AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */, ); name = Telemetry; sourceTree = ""; @@ -1891,6 +1917,8 @@ 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, + CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */, + 1FB7DAAF1F2A4DBD00410606 /* MGLVectorSource+MGLAdditions.h in Headers */, DA88483B1CBAFB8500AB86E3 /* MGLCalloutView.h in Headers */, 35E0CFE61D3E501500188327 /* MGLStyle_Private.h in Headers */, 3510FFF01D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, @@ -1963,6 +1991,7 @@ DA8847F91CBAFA5100AB86E3 /* MGLPolygon.h in Headers */, 4049C2AC1DB6E05500B3F799 /* MGLPointCollection_Private.h in Headers */, DA8847F81CBAFA5100AB86E3 /* MGLPointAnnotation.h in Headers */, + 8989B17C201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */, 353933F21D3FB753003F57D7 /* MGLCircleStyleLayer.h in Headers */, DA8847F31CBAFA5100AB86E3 /* MGLMultiPoint.h in Headers */, 30E578171DAA85520050F07E /* UIImage+MGLAdditions.h in Headers */, @@ -1978,6 +2007,7 @@ DA8847F51CBAFA5100AB86E3 /* MGLOfflineRegion.h in Headers */, DA737EE11D056A4E005BDA16 /* MGLMapViewDelegate.h in Headers */, DA8848851CBB033F00AB86E3 /* FABKitProtocol.h in Headers */, + AC518DFF201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */, DA88481B1CBAFA6200AB86E3 /* MGLGeometry_Private.h in Headers */, 3510FFF91D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */, 3557F7B01E1D27D300CCA5E6 /* MGLDistanceFormatter.h in Headers */, @@ -2038,6 +2068,7 @@ 96E516EF2000594F00A02306 /* NSArray+MGLAdditions.h in Headers */, 96E516F12000596800A02306 /* NSString+MGLAdditions.h in Headers */, 35E0CFE71D3E501500188327 /* MGLStyle_Private.h in Headers */, + CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */, DABFB86D1CBE9A0F00D62B32 /* MGLAnnotationImage.h in Headers */, DABFB8721CBE9A0F00D62B32 /* MGLUserLocation.h in Headers */, 927FBD001F4DB05500F8BF1F /* MGLMapSnapshotter.h in Headers */, @@ -2068,6 +2099,7 @@ 96E516FB20005A4000A02306 /* MGLUserLocationHeadingBeamLayer.h in Headers */, 96E516DC2000547000A02306 /* MGLPolyline_Private.h in Headers */, 353AFA151D65AB17005A69F4 /* NSDate+MGLAdditions.h in Headers */, + AC518E00201BB55A00EBC820 /* MGLTelemetryConfig.h in Headers */, 3510FFFA1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.h in Headers */, DA72620C1DEEE3480043BB89 /* MGLOpenGLStyleLayer.h in Headers */, 35CE61831D4165D9004F2359 /* UIColor+MGLAdditions.h in Headers */, @@ -2099,6 +2131,7 @@ DABFB8631CBE99E500D62B32 /* MGLOfflineRegion.h in Headers */, DA35A2B21CCA141D00E826B2 /* MGLCompassDirectionFormatter.h in Headers */, DAF0D8141DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */, + 8989B17D201A48EB0081CF59 /* MGLHeatmapStyleLayer.h in Headers */, DABFB8731CBE9A9900D62B32 /* Mapbox.h in Headers */, 357FE2DE1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */, 1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */, @@ -2544,6 +2577,8 @@ 409F43FD1E9E781C0048729D /* MGLMapViewDelegateIntegrationTests.swift in Sources */, DA2E88651CC0382C00F24E7B /* MGLStyleTests.mm in Sources */, DA2E88611CC0382C00F24E7B /* MGLGeometryTests.mm in Sources */, + 170C437D2029D97900863DF0 /* MGLHeatmapStyleLayerTests.mm in Sources */, + 170C437C2029D96F00863DF0 /* MGLHeatmapColorTests.mm in Sources */, 357579801D501E09000B822E /* MGLFillStyleLayerTests.mm in Sources */, 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */, DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */, @@ -2656,6 +2691,7 @@ 35B82BFA1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */, DA8848521CBAFB9800AB86E3 /* MGLAPIClient.m in Sources */, 966FCF4E1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */, + 8989B17E201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */, DA8848301CBAFA6200AB86E3 /* NSProcessInfo+MGLAdditions.m in Sources */, 353AFA161D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */, 35D13AC51D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */, @@ -2667,6 +2703,7 @@ DA72620D1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, DA88481A1CBAFA6200AB86E3 /* MGLAccountManager.m in Sources */, 3510FFFB1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */, + AC518E03201BB56000EBC820 /* MGLTelemetryConfig.m in Sources */, DA8848271CBAFA6200AB86E3 /* MGLPolyline.mm in Sources */, DA8848581CBAFB9800AB86E3 /* MGLMapboxEvents.m in Sources */, 35CE61841D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */, @@ -2747,6 +2784,7 @@ DAA4E4311CBB730400178DFB /* MGLMapboxEvents.m in Sources */, 966FCF4F1F3A5C9200F2B6DE /* MGLUserLocationHeadingBeamLayer.m in Sources */, DAA4E4231CBB730400178DFB /* MGLPolygon.mm in Sources */, + 8989B17F201A48EB0081CF59 /* MGLHeatmapStyleLayer.mm in Sources */, 353AFA171D65AB17005A69F4 /* NSDate+MGLAdditions.mm in Sources */, 35D13AC61D3D19DD00AFB4E0 /* MGLFillStyleLayer.mm in Sources */, DAA4E42A1CBB730400178DFB /* NSProcessInfo+MGLAdditions.m in Sources */, @@ -2758,6 +2796,7 @@ DA72620E1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, DAA4E42F1CBB730400178DFB /* MGLCompactCalloutView.m in Sources */, 3510FFFC1D6DCC4700F413B2 /* NSCompoundPredicate+MGLAdditions.mm in Sources */, + AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */, DAA4E4271CBB730400178DFB /* MGLTilePyramidOfflineRegion.mm in Sources */, DAA4E41C1CBB730400178DFB /* MGLAccountManager.m in Sources */, 35CE61851D4165D9004F2359 /* UIColor+MGLAdditions.mm in Sources */, diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index b3662adc7..61e9ad39e 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -95,6 +95,7 @@ custom_categories: - MGLCircleStyleLayer - MGLFillStyleLayer - MGLFillExtrusionStyleLayer + - MGLHeatmapStyleLayer - MGLHillshadeStyleLayer - MGLLineStyleLayer - MGLSymbolStyleLayer diff --git a/platform/ios/src/MGLCameraChangeReason.h b/platform/ios/src/MGLCameraChangeReason.h new file mode 100644 index 000000000..6c6b3636b --- /dev/null +++ b/platform/ios/src/MGLCameraChangeReason.h @@ -0,0 +1,61 @@ +#import "MGLFoundation.h" + +/** + :nodoc: + Bitmask values that describe why a camera move occurred. + + Values of this type are passed to the `MGLMapView`'s delegate in the following methods: + + - `-mapView:shouldChangeFromCamera:toCamera:reason:` + - `-mapView:regionWillChangeWithReason:animated:` + - `-mapView:regionIsChangingWithReason:` + - `-mapView:regionDidChangeWithReason:animated:` + + It's important to note that it's almost impossible to perform a rotate without zooming (in or out), + so if you'll often find `MGLCameraChangeReasonGesturePinch` set alongside `MGLCameraChangeReasonGestureRotate`. + + Since there are several reasons why a zoom or rotation has occurred, it is worth considering + creating a combined constant, for example: + + ``` + static const MGLCameraChangeReason anyZoom = MGLCameraChangeReasonGesturePinch | + MGLCameraChangeReasonGestureZoomIn | + MGLCameraChangeReasonGestureZoomOut | + MGLCameraChangeReasonGestureOneFingerZoom; + + static const MGLCameraChangeReason anyRotation = MGLCameraChangeReasonResetNorth | MGLCameraChangeReasonGestureRotate; + ``` + */ +typedef NS_OPTIONS(NSUInteger, MGLCameraChangeReason) +{ + /// :nodoc: The reason for the camera change has not be specified. + MGLCameraChangeReasonNone = 0, + + /// :nodoc: Set when a public API that moves the camera is called. This may be set for some gestures, + /// for example MGLCameraChangeReasonResetNorth. + MGLCameraChangeReasonProgrammatic = 1 << 0, + + /// :nodoc: The user tapped the compass to reset the map orientation so North is up. + MGLCameraChangeReasonResetNorth = 1 << 1, + + /// :nodoc: The user panned the map. + MGLCameraChangeReasonGesturePan = 1 << 2, + + /// :nodoc: The user pinched to zoom in/out. + MGLCameraChangeReasonGesturePinch = 1 << 3, + + // :nodoc: The user rotated the map. + MGLCameraChangeReasonGestureRotate = 1 << 4, + + /// :nodoc: The user zoomed the map in (one finger double tap). + MGLCameraChangeReasonGestureZoomIn = 1 << 5, + + /// :nodoc: The user zoomed the map out (two finger single tap). + MGLCameraChangeReasonGestureZoomOut = 1 << 6, + + /// :nodoc: The user long pressed on the map for a quick zoom (single tap, then long press and drag up/down). + MGLCameraChangeReasonGestureOneFingerZoom = 1 << 7, + + // :nodoc: The user panned with two fingers to tilt the map (two finger drag). + MGLCameraChangeReasonGestureTilt = 1 << 8 +}; diff --git a/platform/ios/src/MGLLocationManager.m b/platform/ios/src/MGLLocationManager.m index b0d2e17d5..85ef4ca48 100644 --- a/platform/ios/src/MGLLocationManager.m +++ b/platform/ios/src/MGLLocationManager.m @@ -1,9 +1,9 @@ #import "MGLLocationManager.h" +#import "MGLTelemetryConfig.h" #import static const NSTimeInterval MGLLocationManagerHibernationTimeout = 300.0; static const NSTimeInterval MGLLocationManagerHibernationPollInterval = 5.0; -static const CLLocationDistance MGLLocationManagerHibernationRadius = 300.0; static const CLLocationDistance MGLLocationManagerDistanceFilter = 5.0; static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManagerRegionIdentifier.fence.center"; @@ -122,7 +122,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage } - (void)establishRegionMonitoringForLocation:(CLLocation *)location { - CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier]; + CLCircularRegion *region = [[CLCircularRegion alloc] initWithCenter:location.coordinate radius:MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius identifier:MGLLocationManagerRegionIdentifier]; region.notifyOnEntry = NO; region.notifyOnExit = YES; [self.standardLocationManager startMonitoringForRegion:region]; @@ -151,7 +151,7 @@ static NSString * const MGLLocationManagerRegionIdentifier = @"MGLLocationManage if (location.speed > 0.0) { [self startBackgroundTimeoutTimer]; } - if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLLocationManagerHibernationRadius) { + if (self.standardLocationManager.monitoredRegions.count == 0 || location.horizontalAccuracy < MGLTelemetryConfig.sharedConfig.MGLLocationManagerHibernationRadius) { [self establishRegionMonitoringForLocation:location]; } if ([self.delegate respondsToSelector:@selector(locationManager:didUpdateLocations:)]) { diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 1cbbc2287..79b02bef3 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -210,6 +210,8 @@ public: @property (nonatomic) UILongPressGestureRecognizer *quickZoom; @property (nonatomic) UIPanGestureRecognizer *twoFingerDrag; +@property (nonatomic) MGLCameraChangeReason cameraChangeReasonBitmask; + /// Mapping from reusable identifiers to annotation images. @property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier; @@ -506,10 +508,6 @@ public: _doubleTap.numberOfTapsRequired = 2; [self addGestureRecognizer:_doubleTap]; - _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)]; - [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap]; - _singleTapGestureRecognizer.delegate = self; - [self addGestureRecognizer:_singleTapGestureRecognizer]; _twoFingerDrag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleTwoFingerDragGesture:)]; _twoFingerDrag.minimumNumberOfTouches = 2; @@ -534,16 +532,25 @@ public: [_quickZoom requireGestureRecognizerToFail:_doubleTap]; [self addGestureRecognizer:_quickZoom]; + _singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTapGesture:)]; + [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTap]; + _singleTapGestureRecognizer.delegate = self; + [_singleTapGestureRecognizer requireGestureRecognizerToFail:_quickZoom]; + [self addGestureRecognizer:_singleTapGestureRecognizer]; + // observe app activity // [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willTerminate) name:UIApplicationWillTerminateNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sleepGL:) name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wakeGL:) name:UIApplicationDidBecomeActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; + // set initial position // mbgl::CameraOptions options; @@ -551,6 +558,9 @@ public: mbgl::EdgeInsets padding = MGLEdgeInsetsFromNSEdgeInsets(self.contentInset); options.padding = padding; options.zoom = 0; + + _cameraChangeReasonBitmask = MGLCameraChangeReasonNone; + _mbglMap->jumpTo(options); _pendingLatitude = NAN; _pendingLongitude = NAN; @@ -706,7 +716,7 @@ public: { MGLAssertIsMainThread(); - _rendererFrontend->onLowMemory(); + _rendererFrontend->reduceMemoryUse(); } #pragma mark - Layout - @@ -1148,6 +1158,13 @@ public: - (void)sleepGL:(__unused NSNotification *)notification { MGLAssertIsMainThread(); + + // Ideally we would wait until we actually received a memory warning but the bulk of the memory + // we have to release is tied up in GL buffers that we can't touch once we're in the background. + // Compromise position: release everything but currently rendering tiles + // A possible improvement would be to store a copy of the GL buffers that we could use to rapidly + // restart, but that we could also discard in response to a memory warning. + _rendererFrontend->reduceMemoryUse(); if ( ! self.dormant) { @@ -1237,6 +1254,8 @@ public: - (void)handleCompassTapGesture:(__unused id)sender { + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonResetNorth; + [self resetNorthAnimated:YES]; if (self.userTrackingMode == MGLUserTrackingModeFollowWithHeading || @@ -1259,6 +1278,7 @@ public: - (void)notifyGestureDidBegin { BOOL animated = NO; + [self cameraWillChangeAnimated:animated]; _mbglMap->setGestureInProgress(true); _changeDelimiterSuppressionDepth++; @@ -1282,6 +1302,23 @@ public: return _changeDelimiterSuppressionDepth > 0; } +- (BOOL)_shouldChangeFromCamera:(nonnull MGLMapCamera *)oldCamera toCamera:(nonnull MGLMapCamera *)newCamera +{ + // Check delegates first + if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:reason:)]) + { + return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera reason:self.cameraChangeReasonBitmask]; + } + else if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)]) + { + return [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:newCamera]; + } + else + { + return YES; + } +} + - (void)handlePanGesture:(UIPanGestureRecognizer *)pan { if ( ! self.isScrollEnabled) return; @@ -1289,7 +1326,9 @@ public: _mbglMap->cancelTransitions(); MGLMapCamera *oldCamera = self.camera; - + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePan; + if (pan.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePanStart forRecognizer:pan]; @@ -1303,9 +1342,8 @@ public: CGPoint delta = [pan translationInView:pan.view]; MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:delta panGesture:pan]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->moveBy({ delta.x, delta.y }); [pan setTranslation:CGPointZero inView:pan.view]; @@ -1327,9 +1365,8 @@ public: { CGPoint offset = CGPointMake(velocity.x * self.decelerationRate / 4, velocity.y * self.decelerationRate / 4); MGLMapCamera *toCamera = [self cameraByPanningWithTranslation:offset panGesture:pan]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->moveBy({ offset.x, offset.y }, MGLDurationFromTimeInterval(self.decelerationRate)); } @@ -1360,6 +1397,8 @@ public: CGPoint centerPoint = [self anchorPointForGesture:pinch]; MGLMapCamera *oldCamera = self.camera; + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGesturePinch; + if (pinch.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePinchStart forRecognizer:pinch]; @@ -1376,9 +1415,8 @@ public: // Calculates the final camera zoom, has no effect within current map camera. MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); // The gesture recognizer only reports the gesture’s current center @@ -1430,9 +1468,8 @@ public: // Calculates the final camera zoom, this has no effect within current map camera. double zoom = log2(newScale); MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:zoom aroundAnchorPoint:centerPoint]; - - if ([self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] - && ![self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { drift = NO; } else { @@ -1458,7 +1495,9 @@ public: CGPoint centerPoint = [self anchorPointForGesture:rotate]; MGLMapCamera *oldCamera = self.camera; - + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureRotate; + if (rotate.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureRotateStart forRecognizer:rotate]; @@ -1485,9 +1524,8 @@ public: } MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -1505,9 +1543,8 @@ public: CGFloat newDegrees = MGLDegreesFromRadians(newRadians) * -1; MGLMapCamera *toCamera = [self cameraByRotatingToDirection:newDegrees aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setBearing(newDegrees, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }, MGLDurationFromTimeInterval(decelerationRate)); @@ -1552,6 +1589,7 @@ public: } [self deselectAnnotation:self.selectedAnnotation animated:YES]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nextElement); + return; } @@ -1562,7 +1600,7 @@ public: CGRect positionRect = [self positioningRectForAnnotation:annotation defaultCalloutPoint:calloutPoint]; [self selectAnnotation:annotation animated:YES calloutPositioningRect:positionRect]; } - else + else if (self.selectedAnnotation) { [self deselectAnnotation:self.selectedAnnotation animated:YES]; } @@ -1644,6 +1682,8 @@ public: if (doubleTap.state == UIGestureRecognizerStateEnded) { + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomIn; + MGLMapCamera *oldCamera = self.camera; double newZoom = round(self.zoomLevel) + 1.0; @@ -1651,9 +1691,8 @@ public: CGPoint gesturePoint = [self anchorPointForGesture:doubleTap]; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { [self trackGestureEvent:MGLEventGestureDoubleTap forRecognizer:doubleTap]; @@ -1680,9 +1719,13 @@ public: _mbglMap->cancelTransitions(); + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureZoomOut; + if (twoFingerTap.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureTwoFingerSingleTap forRecognizer:twoFingerTap]; + + [self notifyGestureDidBegin]; } else if (twoFingerTap.state == UIGestureRecognizerStateEnded) { @@ -1693,9 +1736,8 @@ public: CGPoint gesturePoint = [self anchorPointForGesture:twoFingerTap]; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:gesturePoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { mbgl::ScreenCoordinate center(gesturePoint.x, gesturePoint.y); _mbglMap->setZoom(newZoom, center, MGLDurationFromTimeInterval(MGLAnimationDuration)); @@ -1715,7 +1757,9 @@ public: if ( ! self.isZoomEnabled) return; _mbglMap->cancelTransitions(); - + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureOneFingerZoom; + if (quickZoom.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGestureQuickZoom forRecognizer:quickZoom]; @@ -1738,9 +1782,8 @@ public: MGLMapCamera *oldCamera = self.camera; MGLMapCamera *toCamera = [self cameraByZoomingToZoomLevel:newZoom aroundAnchorPoint:centerPoint]; - - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setZoom(newZoom, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -1760,6 +1803,8 @@ public: _mbglMap->cancelTransitions(); + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonGestureTilt; + if (twoFingerDrag.state == UIGestureRecognizerStateBegan) { [self trackGestureEvent:MGLEventGesturePitchStart forRecognizer:twoFingerDrag]; @@ -1779,8 +1824,7 @@ public: MGLMapCamera *oldCamera = self.camera; MGLMapCamera *toCamera = [self cameraByTiltingToPitch:pitchNew]; - if (![self.delegate respondsToSelector:@selector(mapView:shouldChangeFromCamera:toCamera:)] || - [self.delegate mapView:self shouldChangeFromCamera:oldCamera toCamera:toCamera]) + if ([self _shouldChangeFromCamera:oldCamera toCamera:toCamera]) { _mbglMap->setPitch(pitchNew, mbgl::ScreenCoordinate { centerPoint.x, centerPoint.y }); } @@ -2278,7 +2322,7 @@ public: - (void)emptyMemoryCache { - _rendererFrontend->onLowMemory(); + _rendererFrontend->reduceMemoryUse(); } - (void)setZoomEnabled:(BOOL)zoomEnabled @@ -2871,6 +2915,8 @@ public: { self.userTrackingMode = MGLUserTrackingModeNone; + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + [self _setCenterCoordinate:centerCoordinate edgePadding:self.contentInset zoomLevel:zoomLevel direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:completion]; } @@ -2920,6 +2966,9 @@ public: } _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + _mbglMap->easeTo(cameraOptions, animationOptions); } @@ -2943,6 +2992,8 @@ public: if (zoomLevel == self.zoomLevel) return; _mbglMap->cancelTransitions(); + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + CGFloat duration = animated ? MGLAnimationDuration : 0; _mbglMap->setZoom(zoomLevel, @@ -3031,6 +3082,9 @@ public: - (void)setVisibleCoordinates:(const CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion { self.userTrackingMode = MGLUserTrackingModeNone; + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + [self _setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:duration animationTimingFunction:function completionHandler:completion]; } @@ -3080,6 +3134,9 @@ public: [self willChangeValueForKey:@"visibleCoordinateBounds"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"visibleCoordinateBounds"]; } @@ -3113,6 +3170,8 @@ public: CGFloat duration = animated ? MGLAnimationDuration : 0; + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + if (self.userTrackingMode == MGLUserTrackingModeNone) { _mbglMap->setBearing(direction, @@ -3197,6 +3256,9 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:edgePadding]; _mbglMap->easeTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -3253,6 +3315,9 @@ public: [self willChangeValueForKey:@"camera"]; _mbglMap->cancelTransitions(); + + self.cameraChangeReasonBitmask |= MGLCameraChangeReasonProgrammatic; + mbgl::CameraOptions cameraOptions = [self cameraOptionsObjectForAnimatingToCamera:camera edgePadding:insets]; _mbglMap->flyTo(cameraOptions, animationOptions); [self didChangeValueForKey:@"camera"]; @@ -3413,6 +3478,13 @@ public: return [self metersPerPointAtLatitude:latitude]; } +#pragma mark - Camera Change Reason - + +- (void)resetCameraChangeReason +{ + self.cameraChangeReasonBitmask = MGLCameraChangeReasonNone; +} + #pragma mark - Styling - - (NS_ARRAY_OF(NSURL *) *)bundledStyleURLs @@ -5354,9 +5426,16 @@ public: } } - if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) + if ( ! [self isSuppressingChangeDelimiters] ) { - [self.delegate mapView:self regionWillChangeAnimated:animated]; + if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeWithReason:animated:)]) + { + [self.delegate mapView:self regionWillChangeWithReason:self.cameraChangeReasonBitmask animated:animated]; + } + else if ([self.delegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) + { + [self.delegate mapView:self regionWillChangeAnimated:animated]; + } } } @@ -5370,8 +5449,12 @@ public: if (!self.scaleBar.hidden) { [(MGLScaleBar *)self.scaleBar setMetersPerPoint:[self metersPerPointAtLatitude:self.centerCoordinate.latitude]]; } - - if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) + + if ([self.delegate respondsToSelector:@selector(mapView:regionIsChangingWithReason:)]) + { + [self.delegate mapView:self regionIsChangingWithReason:self.cameraChangeReasonBitmask]; + } + else if ([self.delegate respondsToSelector:@selector(mapViewRegionIsChanging:)]) { [self.delegate mapViewRegionIsChanging:self]; } @@ -5384,9 +5467,13 @@ public: [self updateCompass]; - if ( ! [self isSuppressingChangeDelimiters] && [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) + if ( ! [self isSuppressingChangeDelimiters]) { - if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) + BOOL respondsToSelector = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]; + BOOL respondsToSelectorWithReason = [self.delegate respondsToSelector:@selector(mapView:regionDidChangeWithReason:animated:)]; + + if ((respondsToSelector || respondsToSelectorWithReason) && + ([UIApplication sharedApplication].applicationState == UIApplicationStateActive)) { _featureAccessibilityElements = nil; _visiblePlaceFeatures = nil; @@ -5398,7 +5485,17 @@ public: UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil); } } - [self.delegate mapView:self regionDidChangeAnimated:animated]; + + if (respondsToSelectorWithReason) + { + [self.delegate mapView:self regionDidChangeWithReason:self.cameraChangeReasonBitmask animated:animated]; + } + else if (respondsToSelector) + { + [self.delegate mapView:self regionDidChangeAnimated:animated]; + } + + [self resetCameraChangeReason]; } } diff --git a/platform/ios/src/MGLMapViewDelegate.h b/platform/ios/src/MGLMapViewDelegate.h index 096711fcb..0368d8413 100644 --- a/platform/ios/src/MGLMapViewDelegate.h +++ b/platform/ios/src/MGLMapViewDelegate.h @@ -1,6 +1,7 @@ #import #import "MGLTypes.h" +#import "MGLCameraChangeReason.h" NS_ASSUME_NONNULL_BEGIN @@ -21,17 +22,80 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark Responding to Map Position Changes +/** + Asks the delegate whether the map view should be allowed to change from the + existing camera to the new camera in response to a user gesture. + + This method is called as soon as the user gesture is recognized. It is not + called in response to a programmatic camera change, such as by setting the + `centerCoordinate` property or calling `-flyToCamera:completionHandler:`. + + This method is called many times during gesturing, so you should avoid performing + complex or performance-intensive tasks in your implementation. + + @param mapView The map view that the user is manipulating. + @param oldCamera The camera representing the viewpoint at the moment the + gesture is recognized. If this method returns `NO`, the map view’s camera + continues to be this camera. + @param newCamera The expected camera after the gesture completes. If this + method returns `YES`, this camera becomes the map view’s camera. + @return A Boolean value indicating whether the map view should stay at + `oldCamera` or change to `newCamera`. + */ +- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera; + +/** + :nodoc: + Asks the delegate whether the map view should be allowed to change from the + existing camera to the new camera in response to a user gesture. + + This method is called as soon as the user gesture is recognized. It is not + called in response to a programmatic camera change, such as by setting the + `centerCoordinate` property or calling `-flyToCamera:completionHandler:`. + + This method is called many times during gesturing, so you should avoid performing + complex or performance-intensive tasks in your implementation. + + @param mapView The map view that the user is manipulating. + @param oldCamera The camera representing the viewpoint at the moment the + gesture is recognized. If this method returns `NO`, the map view’s camera + continues to be this camera. + @param newCamera The expected camera after the gesture completes. If this + method returns `YES`, this camera becomes the map view’s camera. + @param reason The reason for the camera change. + @return A Boolean value indicating whether the map view should stay at + `oldCamera` or change to `newCamera`. + + @note If this method is implemented `-mapView:shouldChangeFromCamera:toCamera:` will not be called. + */ +- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera reason:(MGLCameraChangeReason)reason; + /** Tells the delegate that the viewpoint depicted by the map view is about to change. This method is called whenever the currently displayed map camera will start changing for any reason. - + @param mapView The map view whose viewpoint will change. @param animated Whether the change will cause an animated effect on the map. */ - (void)mapView:(MGLMapView *)mapView regionWillChangeAnimated:(BOOL)animated; +/** + :nodoc: + Tells the delegate that the viewpoint depicted by the map view is about to change. + + This method is called whenever the currently displayed map camera will start + changing for any reason. + + @param mapView The map view whose viewpoint will change. + @param animated Whether the change will cause an animated effect on the map. + @param reason The reason for the camera change. + + @note If this method is implemented `-mapView:regionWillChangeAnimated:` will not be called. + */ +- (void)mapView:(MGLMapView *)mapView regionWillChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated; + /** Tells the delegate that the viewpoint depicted by the map view is changing. @@ -48,6 +112,26 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)mapViewRegionIsChanging:(MGLMapView *)mapView; +/** + :nodoc: + Tells the delegate that the viewpoint depicted by the map view is changing. + + This method is called as the currently displayed map camera changes as part of + an animation, whether due to a user gesture or due to a call to a method such + as `-[MGLMapView setCamera:animated:]`. This method can be called before + `-mapViewDidFinishLoadingMap:` is called. + + During the animation, this method may be called many times to report updates to + the viewpoint. Therefore, your implementation of this method should be as lightweight + as possible to avoid affecting performance. + + @param mapView The map view whose viewpoint is changing. + @param reason The reason for the camera change. + + @note If this method is implemented `-mapViewRegionIsChanging:` will not be called. + */ +- (void)mapView:(MGLMapView *)mapView regionIsChangingWithReason:(MGLCameraChangeReason)reason; + /** Tells the delegate that the viewpoint depicted by the map view has finished changing. @@ -62,26 +146,21 @@ NS_ASSUME_NONNULL_BEGIN - (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated; /** - Asks the delegate whether the map view should be allowed to change from the - existing camera to the new camera in response to a user gesture. - - This method is called as soon as the user gesture is recognized. It is not - called in response to a programmatic camera change, such as by setting the - `centerCoordinate` property or calling `-flyToCamera:completionHandler:`. - - This method is called many times during gesturing, so you should avoid performing - complex or performance-intensive tasks in your implementation. - - @param mapView The map view that the user is manipulating. - @param oldCamera The camera representing the viewpoint at the moment the - gesture is recognized. If this method returns `NO`, the map view’s camera - continues to be this camera. - @param newCamera The expected camera after the gesture completes. If this - method returns `YES`, this camera becomes the map view’s camera. - @return A Boolean value indicating whether the map view should stay at - `oldCamera` or change to `newCamera`. + :nodoc: + Tells the delegate that the viewpoint depicted by the map view has finished + changing. + + This method is called whenever the currently displayed map camera has finished + changing, after any calls to `-mapViewRegionIsChanging:` due to animation. Therefore, + this method can be called before `-mapViewDidFinishLoadingMap:` is called. + + @param mapView The map view whose viewpoint has changed. + @param animated Whether the change caused an animated effect on the map. + @param reason The reason for the camera change. + + @note If this method is implemented `-mapView:regionDidChangeAnimated:` will not be called. */ -- (BOOL)mapView:(MGLMapView *)mapView shouldChangeFromCamera:(MGLMapCamera *)oldCamera toCamera:(MGLMapCamera *)newCamera; +- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated; #pragma mark Loading the Map diff --git a/platform/ios/src/MGLMapboxEvents.m b/platform/ios/src/MGLMapboxEvents.m index d59972f5b..273af5b3b 100644 --- a/platform/ios/src/MGLMapboxEvents.m +++ b/platform/ios/src/MGLMapboxEvents.m @@ -6,6 +6,7 @@ #import "NSException+MGLAdditions.h" #import "MGLAPIClient.h" #import "MGLLocationManager.h" +#import "MGLTelemetryConfig.h" #include #include @@ -172,6 +173,8 @@ const NSTimeInterval MGLFlushInterval = 180; - (instancetype) init { self = [super init]; if (self) { + [MGLTelemetryConfig.sharedConfig configurationFromKey:[[NSUserDefaults standardUserDefaults] objectForKey:MGLMapboxMetricsProfile]]; + _currentAccountTypeValue = @0; _currentMetricsEnabledValue = YES; diff --git a/platform/ios/src/MGLTelemetryConfig.h b/platform/ios/src/MGLTelemetryConfig.h new file mode 100644 index 000000000..527d34429 --- /dev/null +++ b/platform/ios/src/MGLTelemetryConfig.h @@ -0,0 +1,18 @@ +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLTelemetryConfig : NSObject + +@property (nonatomic) CLLocationDistance MGLLocationManagerHibernationRadius; + +extern NSString *const MGLMapboxMetricsProfile; + ++ (nullable instancetype)sharedConfig; + +- (void)configurationFromKey:(NSString *)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/MGLTelemetryConfig.m b/platform/ios/src/MGLTelemetryConfig.m new file mode 100644 index 000000000..828bafb14 --- /dev/null +++ b/platform/ios/src/MGLTelemetryConfig.m @@ -0,0 +1,35 @@ +#import "MGLTelemetryConfig.h" + +static const CLLocationDistance MGLConfigHibernationRadiusDefault = 300.0; +static const CLLocationDistance MGLConfigHibernationRadiusWide = 600.0; + +NSString *const MGLMapboxMetricsProfile = @"MGLMapboxMetricsProfile"; + +static NSString *const MGLConfigHibernationRadiusWideKey = @"WideGeoFence"; + +@implementation MGLTelemetryConfig + +- (instancetype) init { + self = [super init]; + if (self) { + _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusDefault; + } + return self; +} + ++ (nullable instancetype)sharedConfig { + static dispatch_once_t onceToken; + static MGLTelemetryConfig *_sharedConfig; + dispatch_once(&onceToken, ^{ + _sharedConfig = [[self alloc] init]; + }); + return _sharedConfig; +} + +- (void)configurationFromKey:(NSString *)key { + if ([key isEqualToString:MGLConfigHibernationRadiusWideKey]) { + _MGLLocationManagerHibernationRadius = MGLConfigHibernationRadiusWide; + } +} + +@end diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index d105189bb..11720ac68 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -45,6 +45,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLSymbolStyleLayer.h" #import "MGLRasterStyleLayer.h" #import "MGLCircleStyleLayer.h" +#import "MGLHeatmapStyleLayer.h" #import "MGLHillshadeStyleLayer.h" #import "MGLBackgroundStyleLayer.h" #import "MGLOpenGLStyleLayer.h" diff --git a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift index 50f101e86..4d11b000b 100644 --- a/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift +++ b/platform/ios/test/MGLMapViewDelegateIntegrationTests.swift @@ -13,6 +13,10 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapViewRegionIsChanging(_ mapView: MGLMapView) {} + func mapViewRegionIsChanging(_ mapView: MGLMapView, reason: MGLCameraChangeReason) {} + + func mapView(_ mapView: MGLMapView, regionIsChangingWith reason: MGLCameraChangeReason) {} + func mapView(_ mapView: MGLMapView, didChange mode: MGLUserTrackingMode, animated: Bool) {} func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {} @@ -33,10 +37,16 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, didDeselect annotation: MGLAnnotation) {} + func mapView(_ mapView: MGLMapView, didSingleTapAt coordinate: CLLocationCoordinate2D) {} + func mapView(_ mapView: MGLMapView, regionDidChangeAnimated animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionDidChangeWith reason: MGLCameraChangeReason, animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionWillChangeAnimated animated: Bool) {} + func mapView(_ mapView: MGLMapView, regionWillChangeWith reason: MGLCameraChangeReason, animated: Bool) {} + func mapViewDidFailLoadingMap(_ mapView: MGLMapView, withError error: Error) {} func mapView(_ mapView: MGLMapView, didUpdate userLocation: MGLUserLocation?) {} @@ -79,4 +89,5 @@ extension MGLMapViewDelegateIntegrationTests: MGLMapViewDelegate { func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera) -> Bool { return false } + func mapView(_ mapView: MGLMapView, shouldChangeFrom oldCamera: MGLMapCamera, to newCamera: MGLMapCamera, reason: MGLCameraChangeReason) -> Bool { return false } } diff --git a/platform/ios/uitest/MapViewTests.m b/platform/ios/uitest/MapViewTests.m index 4ed3d8939..ba15af918 100644 --- a/platform/ios/uitest/MapViewTests.m +++ b/platform/ios/uitest/MapViewTests.m @@ -538,10 +538,13 @@ userInfo:@{ @"animated" : @(animated) }]; } -- (void)mapView:(MGLMapView *)mapView regionDidChangeAnimated:(BOOL)animated { +- (void)mapView:(MGLMapView *)mapView regionDidChangeWithReason:(MGLCameraChangeReason)reason animated:(BOOL)animated { + [[NSNotificationCenter defaultCenter] postNotificationName:@"regionDidChangeAnimated" object:mapView - userInfo:@{ @"animated" : @(animated) }]; + userInfo:@{ @"animated" : @(animated), + @"reason" : @(reason) + }]; } - (void)testDelegatesStartStopLocatingUser { diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index d35f396a2..0df8e9a80 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -4,20 +4,24 @@ ### Styles and rendering +* Added support for a new layer type: `MGLHeatmapStyleLayer`, a powerful way to visualize point data distributions using heatmaps, fully customizable through runtime styling. [#11046](https://github.com/mapbox/mapbox-gl-native/pull/11046) * The layout and paint properties on subclasses of `MGLStyleLayer` are now of type `NSExpression` instead of `MGLStyleValue`. A new “Predicates and Expressions” guide provides an overview of the supported operators. ([#10726](https://github.com/mapbox/mapbox-gl-native/pull/10726)) * Added an `MGLComputedShapeSource` class that allows applications to supply vector data to a style layer on a per-tile basis. ([#9983](https://github.com/mapbox/mapbox-gl-native/pull/9983)) * A style can now display smooth hillshading and customize its appearance at runtime using the `MGLHillshadeStyleLayer` class. Hillshading is based on a rasterized digital elevation model supplied by the `MGLRasterDEMSource` class. ([#10642](https://github.com/mapbox/mapbox-gl-native/pull/10642)) * The `MGLSymbolStyleLayer.textFontNames` property can now depend on a feature’s attributes. ([#10850](https://github.com/mapbox/mapbox-gl-native/pull/10850)) +* Properties such as `MGLSymbolStyleLayer.iconAllowsOverlap` and `MGLSymbolStyleLayer.iconIgnoresPlacement` now account for symbols in other sources. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436)) ### Map rendering * Improved the reliability of collision detection between symbols near the edges of tiles, as well as between symbols when the map is tilted. It is no longer necessary to enable `MGLSymbolStyleLayer.symbolAvoidsEdges` to prevent symbols in adjacent tiles from overlapping with each other. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436)) * Symbols can fade in and out as the map pans, rotates, or tilts. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436)) * Properties such as `MGLSymbolStyleLayer.iconAllowsOverlap` and `MGLSymbolStyleLayer.iconIgnoresPlacement` now account for symbols in other sources. ([#10436](https://github.com/mapbox/mapbox-gl-native/pull/10436)) +* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141)) * Fixed an issue preventing a dynamically-added `MGLRasterStyleLayer` from drawing until the map pans. ([#10270](https://github.com/mapbox/mapbox-gl-native/pull/10270)) * Fixed an issue preventing `MGLImageSource`s from drawing on the map when the map is zoomed in and tilted. ([#10677](https://github.com/mapbox/mapbox-gl-native/pull/10677)) * Improved the sharpness of raster tiles on Retina displays. ([#10984](https://github.com/mapbox/mapbox-gl-native/pull/10984)) * Fixed a crash parsing a malformed style. ([#11001](https://github.com/mapbox/mapbox-gl-native/pull/11001)) +* Added the `MGLTileSourceOptionTileCoordinateBounds` option to create an `MGLTileSource` that only supplies tiles within a specific geographic bounding box. ([#11141](https://github.com/mapbox/mapbox-gl-native/pull/11141)) ### Map snapshots @@ -28,6 +32,7 @@ * Added Danish and Hebrew localizations. ([#10967](https://github.com/mapbox/mapbox-gl-native/pull/10967), [#11136](https://github.com/mapbox/mapbox-gl-native/pull/11134)) * Feature querying results now account for the `MGLSymbolStyleLayer.circleStrokeWidth` property. ([#10897](https://github.com/mapbox/mapbox-gl-native/pull/10897)) +* Added a Hebrew localization. ([#10967](https://github.com/mapbox/mapbox-gl-native/pull/10967)) ## v0.6.1 - January 16, 2018 diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json new file mode 100644 index 000000000..10226ca23 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "heatmap.pdf", + "language-direction" : "left-to-right" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf new file mode 100644 index 000000000..88195c373 Binary files /dev/null and b/platform/macos/app/Assets.xcassets/Layers/heatmap.imageset/heatmap.pdf differ diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 602ed1aa4..7d39f9334 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -726,8 +726,14 @@ NS_ARRAY_OF(id ) *MBXFlattenedShapes(NS_ARRAY_OF(id + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index 4a16066a2..de838cd78 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 682ccd99f..b4dccb620 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 e42885f87..5af3d17b5 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 07D9474D1F67441B00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */; }; 07F8E2F71F674C8800F794BB /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 07F8E2F81F674C9000F794BB /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */; }; + 170A82BF201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */; }; + 170A82C4201FB6EC00943087 /* MGLHeatmapColorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */; }; 1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */; }; 1F7454A31ECFB00300021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */; }; 1F7454A41ECFB00300021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A11ECFB00300021D39 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -85,6 +87,9 @@ 55D120A31F7906E6004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; }; 55D120A51F790A0C004B6D81 /* libmbgl-filesource.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 55D120A41F7906E6004B6D81 /* libmbgl-filesource.a */; }; 55E2AD111E5B0A6900E8C587 /* MGLOfflineStorageTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */; }; + 89462399200D199100DA8EF2 /* heatmap.json in Resources */ = {isa = PBXBuildFile; fileRef = 89462398200D199100DA8EF2 /* heatmap.json */; }; + 8946239D200E744800DA8EF2 /* MGLHeatmapStyleLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 894623A0200E748000DA8EF2 /* MGLHeatmapStyleLayer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */; }; 92092EF01F5EB10E00AF5130 /* MGLMapSnapshotter.h in Headers */ = {isa = PBXBuildFile; fileRef = 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92092EF11F5EB10E00AF5130 /* MGLMapSnapshotter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */; }; 920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */; }; @@ -298,6 +303,8 @@ 07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = ""; }; 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = ""; }; 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = ""; }; + 170A82BE201BDD1B00943087 /* MGLHeatmapStyleLayerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayerTests.mm; sourceTree = ""; }; + 170A82C2201FAFF800943087 /* MGLHeatmapColorTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapColorTests.mm; sourceTree = ""; }; 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = ""; }; 1F7454A11ECFB00300021D39 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; @@ -369,6 +376,9 @@ 55D9B4B01D005D3900C1CCE2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 55E2AD101E5B0A6900E8C587 /* MGLOfflineStorageTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLOfflineStorageTests.mm; path = ../../darwin/test/MGLOfflineStorageTests.mm; sourceTree = ""; }; 55FE0E8D1D100A0900FD240B /* config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = config.xcconfig; path = ../../build/macos/config.xcconfig; sourceTree = ""; }; + 89462398200D199100DA8EF2 /* heatmap.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = heatmap.json; sourceTree = ""; }; + 8946239A200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLHeatmapStyleLayer.h; sourceTree = ""; }; + 8946239B200E73CA00DA8EF2 /* MGLHeatmapStyleLayer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLHeatmapStyleLayer.mm; sourceTree = ""; }; 92092EEE1F5EB10E00AF5130 /* MGLMapSnapshotter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLMapSnapshotter.h; sourceTree = ""; }; 92092EEF1F5EB10E00AF5130 /* MGLMapSnapshotter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLMapSnapshotter.mm; sourceTree = ""; }; 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLSourceQueryTests.m; sourceTree = ""; }; @@ -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 7ea9a9dd1..0e4b546cf 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 3e66aaa78..ac14df022 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json index ab187a86b..42751da30 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 fc346cdcd..f6d547319 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 8b319b045..bc18eaba5 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 aaae19965..d1d597bda 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 2675d8786..414b65255 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 -#include -#include #else #include -#include #endif #include @@ -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"); @@ -164,6 +167,27 @@ std::unique_ptr toStyleImage(const QString &id, const QImage \sa contextMode() */ +/*! + \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 @@ -200,6 +224,7 @@ std::unique_ptr 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) @@ -229,6 +254,31 @@ void QMapboxGLSettings::setContextMode(GLContextMode mode) m_contextMode = 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; + auto size = sanitizedSize(size_); - d_ptr->size = size; - d_ptr->fbSize = framebufferSize; + if (d_ptr->mapObj->getSize() == size) + return; - d_ptr->mapObj->setSize(sanitizedSize(size)); -} - -/*! - 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); } /*! @@ -1467,6 +1502,51 @@ void QMapboxGL::setFilter(const QString& layer, const QVariant& filter) qWarning() << "Layer doesn't support filters"; } +/*! + 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 @@ -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(); } /*! @@ -1509,6 +1594,16 @@ void QMapboxGL::connectionEstablished() \sa render() */ +/*! + \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) @@ -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::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::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( - std::make_unique(*this, pixelRatio, *fileSourceObj, *threadPool, - static_cast(settings.contextMode())), - *this); - connect(frontend.get(), SIGNAL(updated()), this, SLOT(invalidate())); - - mapObj = std::make_unique( - *frontend, - *this, sanitizedSize(size), - pixelRatio, *fileSourceObj, *threadPool, - mbgl::MapMode::Continuous, - static_cast(settings.constrainMode()), - static_cast(settings.viewportMode())); + // Setup MapObserver + m_mapObserver = std::make_unique(this); qRegisterMetaType("QMapboxGL::MapChange"); - fileSourceObj->setAccessToken(settings.accessToken().toStdString()); - fileSourceObj->setAPIBaseURL(settings.apiBaseUrl().toStdString()); + connect(m_mapObserver.get(), SIGNAL(mapChanged(QMapboxGL::MapChange)), q, SIGNAL(mapChanged(QMapboxGL::MapChange))); + connect(m_mapObserver.get(), SIGNAL(copyrightsChanged(QString)), q, SIGNAL(copyrightsChanged(QString))); - 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); + // Setup the Map object + mapObj = std::make_unique( + *this, // RendererFrontend + *m_mapObserver, + sanitizedSize(size), + m_pixelRatio, *m_fileSourceObj, *m_threadPool, + static_cast(settings.mapMode()), + static_cast(settings.constrainMode()), + static_cast(settings.viewportMode())); + + // 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::updateAssumedState() { - assumeFramebufferBinding(fbObject); -#if QT_VERSION >= 0x050600 - assumeViewport(0, 0, getFramebufferSize()); -#endif -} - -void QMapboxGLPrivate::bind() { - setFramebufferBinding(fbObject); - setViewport(0, 0, getFramebufferSize()); -} - -void QMapboxGLPrivate::invalidate() +void QMapboxGLPrivate::update(std::shared_ptr parameters) { - if (!dirty) { - emit needsRendering(); - dirty = true; + std::lock_guard lock(m_mapRendererMutex); + + if (!m_mapRenderer) { + return; } + + m_mapRenderer->updateParameters(std::move(parameters)); + + requestRendering(); +} + +void QMapboxGLPrivate::setObserver(mbgl::RendererObserver &observer) +{ + m_rendererObserver = std::make_shared( + *mbgl::util::RunLoop::Get(), observer); + + std::lock_guard lock(m_mapRendererMutex); + + if (m_mapRenderer) { + m_mapRenderer->setObserver(m_rendererObserver); + } +} + +void QMapboxGLPrivate::createRenderer() +{ + std::lock_guard lock(m_mapRendererMutex); + + if (m_mapRenderer) { + return; + } + + m_mapRenderer = std::make_unique( + m_pixelRatio, + *m_fileSourceObj, + *m_threadPool, + m_mode + ); + + connect(m_mapRenderer.get(), SIGNAL(needsRendering()), this, SLOT(requestRendering())); + + m_mapRenderer->setObserver(m_rendererObserver); +} + +void QMapboxGLPrivate::destroyRenderer() +{ + std::lock_guard lock(m_mapRendererMutex); + + m_mapRenderer.reset(); } void QMapboxGLPrivate::render() { - frontend->render(); -} + std::lock_guard lock(m_mapRendererMutex); -void QMapboxGLPrivate::onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) -{ - if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) { - emit mapChanged(QMapboxGL::MapChangeRegionWillChange); - } else { - emit mapChanged(QMapboxGL::MapChangeRegionWillChangeAnimated); + if (!m_mapRenderer) { + createRenderer(); } -} -void QMapboxGLPrivate::onCameraIsChanging() -{ - emit mapChanged(QMapboxGL::MapChangeRegionIsChanging); -} - -void QMapboxGLPrivate::onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) -{ - if (mode == mbgl::MapObserver::CameraChangeMode::Immediate) { - emit mapChanged(QMapboxGL::MapChangeRegionDidChange); - } else { - emit mapChanged(QMapboxGL::MapChangeRegionDidChangeAnimated); +#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; } -} - -void QMapboxGLPrivate::onWillStartLoadingMap() -{ - emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap); -} - -void QMapboxGLPrivate::onDidFinishLoadingMap() -{ - emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingMap); -} - -void QMapboxGLPrivate::onDidFailLoadingMap(std::exception_ptr) -{ - emit mapChanged(QMapboxGL::MapChangeDidFailLoadingMap); -} - -void QMapboxGLPrivate::onWillStartRenderingFrame() -{ - emit mapChanged(QMapboxGL::MapChangeWillStartRenderingFrame); -} - -void QMapboxGLPrivate::onDidFinishRenderingFrame(mbgl::MapObserver::RenderMode mode) -{ - if (mode == mbgl::MapObserver::RenderMode::Partial) { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrame); - } else { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingFrameFullyRendered); - } -} - -void QMapboxGLPrivate::onWillStartRenderingMap() -{ - emit mapChanged(QMapboxGL::MapChangeWillStartLoadingMap); -} - -void QMapboxGLPrivate::onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) -{ - if (mode == mbgl::MapObserver::RenderMode::Partial) { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMap); - } else { - emit mapChanged(QMapboxGL::MapChangeDidFinishRenderingMapFullyRendered); - } -} - -void QMapboxGLPrivate::onDidFinishLoadingStyle() -{ - emit mapChanged(QMapboxGL::MapChangeDidFinishLoadingStyle); -} - -void QMapboxGLPrivate::onSourceChanged(mbgl::style::Source&) -{ - 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(); - } - 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(thisContext->getProcAddress(name)); #endif + + m_renderQueued.clear(); + m_mapRenderer->render(); } -void QMapboxGLPrivate::connectionEstablished() +void QMapboxGLPrivate::setFramebufferObject(quint32 fbo, const QSize& size) { - mbgl::NetworkStatus::Reachable(); + std::lock_guard lock(m_mapRendererMutex); + + if (!m_mapRenderer) { + createRenderer(); + } + + m_mapRenderer->updateFramebuffer(fbo, sanitizedSize(size)); +} + +void QMapboxGLPrivate::requestRendering() +{ + 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 000000000..e180ed8fd --- /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 000000000..c9d0581a9 --- /dev/null +++ b/platform/qt/src/qmapboxgl_map_observer.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "qmapboxgl.hpp" + +#include +#include + +#include + +#include +#include + +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 000000000..7a9d1f6f7 --- /dev/null +++ b/platform/qt/src/qmapboxgl_map_renderer.cpp @@ -0,0 +1,86 @@ +#include "qmapboxgl_map_renderer.hpp" + +#include + +QMapboxGLMapRenderer::QMapboxGLMapRenderer(qreal pixelRatio, + mbgl::DefaultFileSource &fs, mbgl::ThreadPool &tp, QMapboxGLSettings::GLContextMode mode) + : m_renderer(std::make_unique(m_backend, pixelRatio, fs, tp, static_cast(mode))) + , m_threadWithScheduler(Scheduler::GetCurrent() != nullptr) +{ +} + +QMapboxGLMapRenderer::~QMapboxGLMapRenderer() +{ + MBGL_VERIFY_THREAD(tid); +} + +void QMapboxGLMapRenderer::schedule(std::weak_ptr mailbox) +{ + std::lock_guard 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 newParameters) +{ + std::lock_guard 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 params; + { + // Lock on the parameters + std::lock_guard 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> taskQueue; + { + std::unique_lock lock(m_taskQueueMutex); + std::swap(taskQueue, m_taskQueue); + } + + while (!taskQueue.empty()) { + mbgl::Mailbox::maybeReceive(taskQueue.front()); + taskQueue.pop(); + } + } +} + +void QMapboxGLMapRenderer::setObserver(std::shared_ptr 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 000000000..adba11de5 --- /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 +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +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 scheduled) final; + + void render(); + void updateFramebuffer(quint32 fbo, const mbgl::Size &size); + void setObserver(std::shared_ptr); + + // Thread-safe, called by the Frontend + void updateParameters(std::shared_ptr); + +signals: + void needsRendering(); + +private: + MBGL_STORE_THREAD(tid) + + Q_DISABLE_COPY(QMapboxGLMapRenderer) + + std::mutex m_updateMutex; + std::shared_ptr m_updateParameters; + + QMapboxGLRendererBackend m_backend; + std::unique_ptr m_renderer; + + std::mutex m_taskQueueMutex; + std::queue> m_taskQueue; + + bool m_threadWithScheduler; +}; diff --git a/platform/qt/src/qmapboxgl_p.hpp b/platform/qt/src/qmapboxgl_p.hpp index f947c09f4..51c7cb8fc 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 #include -#include -#include +#include #include -#include #include +#include +#include #include #include +#include #include -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) 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 fileSourceObj; - std::shared_ptr threadPool; - std::unique_ptr frontend; std::unique_ptr 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 > m_resourceTransform; + Q_DISABLE_COPY(QMapboxGLPrivate) + + std::recursive_mutex m_mapRendererMutex; + std::shared_ptr m_rendererObserver; + + std::unique_ptr m_mapObserver; + std::shared_ptr m_fileSourceObj; + std::shared_ptr m_threadPool; + std::unique_ptr m_mapRenderer; + std::unique_ptr> 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 000000000..917741f5c --- /dev/null +++ b/platform/qt/src/qmapboxgl_renderer_backend.cpp @@ -0,0 +1,49 @@ +#include "qmapboxgl_renderer_backend.hpp" + +#include + +#if QT_VERSION >= 0x050000 +#include +#else +#include +#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(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 000000000..de66b035f --- /dev/null +++ b/platform/qt/src/qmapboxgl_renderer_backend.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "qmapboxgl.hpp" + +#include +#include +#include + +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 ea60851eb..000000000 --- a/platform/qt/src/qmapboxgl_renderer_frontend_p.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "qmapboxgl_renderer_frontend_p.hpp" - -#include -#include - -QMapboxGLRendererFrontend::QMapboxGLRendererFrontend(std::unique_ptr 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 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 c5e2bacc3..000000000 --- a/platform/qt/src/qmapboxgl_renderer_frontend_p.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include - -#include - -namespace mbgl { - class Renderer; -} // namespace mbgl - -class QMapboxGLRendererFrontend : public QObject, public mbgl::RendererFrontend -{ - Q_OBJECT - -public: - explicit QMapboxGLRendererFrontend(std::unique_ptr, mbgl::RendererBackend&); - ~QMapboxGLRendererFrontend() override; - - void reset() override; - void setObserver(mbgl::RendererObserver&) override; - - void update(std::shared_ptr) override; - -public slots: - void render(); - -signals: - void updated(); - -private: - std::unique_ptr renderer; - mbgl::RendererBackend& backend; - std::shared_ptr updateParameters; -}; diff --git a/platform/qt/src/qmapboxgl_renderer_observer.hpp b/platform/qt/src/qmapboxgl_renderer_observer.hpp new file mode 100644 index 000000000..ee340113f --- /dev/null +++ b/platform/qt/src/qmapboxgl_renderer_observer.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +// 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(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 mailbox; + mbgl::ActorRef delegate; +}; diff --git a/platform/qt/src/qt_logging.cpp b/platform/qt/src/qt_logging.cpp old mode 100755 new mode 100644 diff --git a/platform/qt/test/qmapboxgl.test.cpp b/platform/qt/test/qmapboxgl.test.cpp index 932460b93..2a56b346a 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 46c097d51..b1eeffb8a 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 ff40244f9..6ddb787f1 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 00daee70d..196adc0b3 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 38ca5ccd0..a237100d1 100644 --- a/src/mbgl/annotation/render_annotation_source.cpp +++ b/src/mbgl/annotation/render_annotation_source.cpp @@ -73,8 +73,8 @@ std::vector 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 aa2578d4e..e812ec288 100644 --- a/src/mbgl/annotation/render_annotation_source.hpp +++ b/src/mbgl/annotation/render_annotation_source.hpp @@ -33,7 +33,7 @@ public: std::vector 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 78134dadc..7fa98950e 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(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 507a51661..817d3cc9c 100644 --- a/src/mbgl/geometry/dem_data.hpp +++ b/src/mbgl/geometry/dem_data.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -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 c6594a3a7..e394f4350 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 b6244d58c..f40cfa1f2 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -60,6 +60,16 @@ static_assert(std::is_same, GLenum>::value static_assert(underlying_type(TextureFormat::RGBA) == GL_RGBA, "OpenGL type mismatch"); static_assert(underlying_type(TextureFormat::Alpha) == GL_ALPHA, "OpenGL type mismatch"); +static_assert(std::is_same, GLenum>::value, "OpenGL type mismatch"); +static_assert(underlying_type(TextureType::UnsignedByte) == GL_UNSIGNED_BYTE, "OpenGL type mismatch"); + +#if MBGL_USE_GLES2 && GL_HALF_FLOAT_OES +static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_OES, "OpenGL type mismatch"); +#endif +#if !MBGL_USE_GLES2 && GL_HALF_FLOAT_ARB +static_assert(underlying_type(TextureType::HalfFloat) == GL_HALF_FLOAT_ARB, "OpenGL type mismatch"); +#endif + static_assert(underlying_type(UniformDataType::Float) == GL_FLOAT, "OpenGL type mismatch"); static_assert(underlying_type(UniformDataType::FloatVec2) == GL_FLOAT_VEC2, "OpenGL type mismatch"); static_assert(underlying_type(UniformDataType::FloatVec3) == GL_FLOAT_VEC3, "OpenGL type mismatch"); @@ -116,6 +126,19 @@ void Context::initializeExtensions(const std::function(fn); #endif +#if MBGL_USE_GLES2 + constexpr const char* halfFloatExtensionName = "OES_texture_half_float"; + constexpr const char* halfFloatColorBufferExtensionName = "EXT_color_buffer_half_float"; +#else + constexpr const char* halfFloatExtensionName = "ARB_half_float_pixel"; + constexpr const char* halfFloatColorBufferExtensionName = "ARB_color_buffer_float"; +#endif + if (strstr(extensions, halfFloatExtensionName) != nullptr && + strstr(extensions, halfFloatColorBufferExtensionName) != nullptr) { + + supportsHalfFloatTextures = true; + } + if (!supportsVertexArrays()) { Log::Warning(Event::OpenGL, "Not using Vertex Array Objects"); } @@ -491,10 +514,10 @@ Context::createFramebuffer(const Texture& color, } UniqueTexture -Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit) { +Context::createTexture(const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) { auto obj = createTexture(); pixelStoreUnpack = { 1 }; - updateTexture(obj, size, data, format, unit); + updateTexture(obj, size, data, format, unit, type); // We are using clamp to edge here since OpenGL ES doesn't allow GL_REPEAT on NPOT textures. // We use those when the pixelRatio isn't a power of two, e.g. on iPhone 6 Plus. MBGL_CHECK_ERROR(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); @@ -505,11 +528,11 @@ Context::createTexture(const Size size, const void* data, TextureFormat format, } void Context::updateTexture( - TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit) { + TextureID id, const Size size, const void* data, TextureFormat format, TextureUnit unit, TextureType type) { activeTextureUnit = unit; texture[unit] = id; MBGL_CHECK_ERROR(glTexImage2D(GL_TEXTURE_2D, 0, static_cast(format), size.width, - size.height, 0, static_cast(format), GL_UNSIGNED_BYTE, + size.height, 0, static_cast(format), static_cast(type), data)); } diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index 14f078367..67624288e 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -125,23 +125,29 @@ public: // Create a texture from an image with data. template - Texture createTexture(const Image& image, TextureUnit unit = 0) { + Texture createTexture(const Image& image, + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - return { image.size, createTexture(image.size, image.data.get(), format, unit) }; + return { image.size, createTexture(image.size, image.data.get(), format, unit, type) }; } template - void updateTexture(Texture& obj, const Image& image, TextureUnit unit = 0) { + void updateTexture(Texture& obj, + const Image& image, + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit); + updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit, type); obj.size = image.size; } // Creates an empty texture with the specified dimensions. Texture createTexture(const Size size, TextureFormat format = TextureFormat::RGBA, - TextureUnit unit = 0) { - return { size, createTexture(size, nullptr, format, unit) }; + TextureUnit unit = 0, + TextureType type = TextureType::UnsignedByte) { + return { size, createTexture(size, nullptr, format, unit, type) }; } void bindTexture(Texture&, @@ -232,6 +238,8 @@ public: State pixelTransferStencil; #endif // MBGL_USE_GLES2 + bool supportsHalfFloatTextures = false; + private: State stencilFunc; State stencilMask; @@ -259,8 +267,8 @@ private: void updateVertexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); UniqueBuffer createIndexBuffer(const void* data, std::size_t size, const BufferUsage usage); void updateIndexBuffer(UniqueBuffer& buffer, const void* data, std::size_t size); - UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit); - void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit); + UniqueTexture createTexture(Size size, const void* data, TextureFormat, TextureUnit, TextureType); + void updateTexture(TextureID, Size size, const void* data, TextureFormat, TextureUnit, TextureType); UniqueFramebuffer createFramebuffer(); UniqueRenderbuffer createRenderbuffer(RenderbufferType, Size size); std::unique_ptr readFramebuffer(Size, TextureFormat, bool flip); diff --git a/src/mbgl/gl/types.hpp b/src/mbgl/gl/types.hpp index da08195e5..376a784a0 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 5d7a6474c..c677c84d5 100644 --- a/src/mbgl/programs/attributes.hpp +++ b/src/mbgl/programs/attributes.hpp @@ -30,7 +30,7 @@ MBGL_DEFINE_ATTRIBUTE(int16_t, 2, a_anchor_pos); MBGL_DEFINE_ATTRIBUTE(uint16_t, 2, a_texture_pos); MBGL_DEFINE_ATTRIBUTE(int16_t, 4, a_normal_ed); MBGL_DEFINE_ATTRIBUTE(uint8_t, 1, a_fade_opacity); -MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); +MBGL_DEFINE_ATTRIBUTE(uint8_t, 2, a_placed); template struct a_data { @@ -142,6 +142,11 @@ struct a_halo_blur { using Type = gl::Attribute; }; +struct a_weight { + static auto name() { return "a_weight"; } + using Type = gl::Attribute; +}; + } // namespace attributes struct PositionOnlyLayoutAttributes : gl::Attributes< diff --git a/src/mbgl/programs/collision_box_program.hpp b/src/mbgl/programs/collision_box_program.hpp index 8d712a3df..6e75adf36 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 000000000..67f84fbd5 --- /dev/null +++ b/src/mbgl/programs/heatmap_program.cpp @@ -0,0 +1,7 @@ +#include + +namespace mbgl { + +static_assert(sizeof(HeatmapLayoutVertex) == 4, "expected HeatmapLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_program.hpp b/src/mbgl/programs/heatmap_program.hpp new file mode 100644 index 000000000..2d9b80404 --- /dev/null +++ b/src/mbgl/programs/heatmap_program.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(float, u_intensity); +} // namespace uniforms + +class HeatmapProgram : public Program< + shaders::heatmap, + gl::Triangle, + gl::Attributes< + attributes::a_pos>, + gl::Uniforms< + uniforms::u_intensity, + uniforms::u_matrix, + uniforms::heatmap::u_extrude_scale>, + style::HeatmapPaintProperties> +{ +public: + using Program::Program; + + /* + * @param {number} x vertex position + * @param {number} y vertex position + * @param {number} ex extrude normal + * @param {number} ey extrude normal + */ + static LayoutVertex vertex(Point p, float ex, float ey) { + return LayoutVertex { + {{ + static_cast((p.x * 2) + ((ex + 1) / 2)), + static_cast((p.y * 2) + ((ey + 1) / 2)) + }} + }; + } +}; + +using HeatmapLayoutVertex = HeatmapProgram::LayoutVertex; +using HeatmapAttributes = HeatmapProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_texture_program.cpp b/src/mbgl/programs/heatmap_texture_program.cpp new file mode 100644 index 000000000..3b0e24eab --- /dev/null +++ b/src/mbgl/programs/heatmap_texture_program.cpp @@ -0,0 +1,7 @@ +#include + +namespace mbgl { + +static_assert(sizeof(HeatmapTextureLayoutVertex) == 4, "expected HeatmapTextureLayoutVertex size"); + +} // namespace mbgl diff --git a/src/mbgl/programs/heatmap_texture_program.hpp b/src/mbgl/programs/heatmap_texture_program.hpp new file mode 100644 index 000000000..7afe8060d --- /dev/null +++ b/src/mbgl/programs/heatmap_texture_program.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +namespace uniforms { +MBGL_DEFINE_UNIFORM_SCALAR(gl::TextureUnit, u_color_ramp); +} // namespace uniforms + +class HeatmapTextureProgram : public Program< + shaders::heatmap_texture, + gl::Triangle, + gl::Attributes, + gl::Uniforms< + uniforms::u_matrix, + uniforms::u_world, + uniforms::u_image, + uniforms::u_color_ramp, + uniforms::u_opacity>, + style::Properties<>> { +public: + using Program::Program; + + static LayoutVertex layoutVertex(Point p) { + return LayoutVertex{ + {{ + p.x, + p.y + }} + }; + } +}; + +using HeatmapTextureLayoutVertex = HeatmapTextureProgram::LayoutVertex; +using HeatmapTextureAttributes = HeatmapTextureProgram::Attributes; + +} // namespace mbgl diff --git a/src/mbgl/programs/hillshade_prepare_program.hpp b/src/mbgl/programs/hillshade_prepare_program.hpp index 0f31a22df..76638afdd 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 f533a6f63..b703323d9 100644 --- a/src/mbgl/programs/programs.hpp +++ b/src/mbgl/programs/programs.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include #include @@ -30,6 +32,8 @@ public: fillPattern(context, programParameters), fillOutline(context, programParameters), fillOutlinePattern(context, programParameters), + heatmap(context, programParameters), + heatmapTexture(context, programParameters), hillshade(context, programParameters), hillshadePrepare(context, programParameters), line(context, programParameters), @@ -55,6 +59,8 @@ public: ProgramMap fillPattern; ProgramMap fillOutline; ProgramMap fillOutlinePattern; + ProgramMap heatmap; + HeatmapTextureProgram heatmapTexture; HillshadeProgram hillshade; HillshadePrepareProgram hillshadePrepare; ProgramMap line; diff --git a/src/mbgl/programs/uniforms.hpp b/src/mbgl/programs/uniforms.hpp index 184f42e50..071a27c80 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 000000000..a185e04ad --- /dev/null +++ b/src/mbgl/renderer/buckets/heatmap_bucket.cpp @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +using namespace style; + +HeatmapBucket::HeatmapBucket(const BucketParameters& parameters, const std::vector& layers) + : mode(parameters.mode) { + for (const auto& layer : layers) { + paintPropertyBinders.emplace( + std::piecewise_construct, + std::forward_as_tuple(layer->getID()), + std::forward_as_tuple( + layer->as()->evaluated, + parameters.tileID.overscaledZ)); + } +} + +void HeatmapBucket::upload(gl::Context& context) { + vertexBuffer = context.createVertexBuffer(std::move(vertices)); + indexBuffer = context.createIndexBuffer(std::move(triangles)); + + for (auto& pair : paintPropertyBinders) { + pair.second.upload(context); + } + + uploaded = true; +} + +bool HeatmapBucket::hasData() const { + return !segments.empty(); +} + +void HeatmapBucket::addFeature(const GeometryTileFeature& feature, + const GeometryCollection& geometry) { + constexpr const uint16_t vertexLength = 4; + + for (auto& points : geometry) { + for(auto& point : points) { + auto x = point.x; + auto y = point.y; + + // Do not include points that are outside the tile boundaries. + // Include all points in Still mode. You need to include points from + // neighbouring tiles so that they are not clipped at tile boundaries. + if ((mode == MapMode::Continuous) && + (x < 0 || x >= util::EXTENT || y < 0 || y >= util::EXTENT)) continue; + + if (segments.empty() || segments.back().vertexLength + vertexLength > std::numeric_limits::max()) { + // Move to a new segments because the old one can't hold the geometry. + segments.emplace_back(vertices.vertexSize(), triangles.indexSize()); + } + + // this geometry will be of the Point type, and we'll derive + // two triangles from it. + // + // ┌─────────┐ + // │ 4 3 │ + // │ │ + // │ 1 2 │ + // └─────────┘ + // + vertices.emplace_back(HeatmapProgram::vertex(point, -1, -1)); // 1 + vertices.emplace_back(HeatmapProgram::vertex(point, 1, -1)); // 2 + vertices.emplace_back(HeatmapProgram::vertex(point, 1, 1)); // 3 + vertices.emplace_back(HeatmapProgram::vertex(point, -1, 1)); // 4 + + auto& segment = segments.back(); + assert(segment.vertexLength <= std::numeric_limits::max()); + uint16_t index = segment.vertexLength; + + // 1, 2, 3 + // 1, 4, 3 + triangles.emplace_back(index, index + 1, index + 2); + triangles.emplace_back(index, index + 3, index + 2); + + segment.vertexLength += vertexLength; + segment.indexLength += 6; + } + } + + for (auto& pair : paintPropertyBinders) { + pair.second.populateVertexVectors(feature, vertices.vertexSize()); + } +} + +float HeatmapBucket::getQueryRadius(const RenderLayer& layer) const { + (void)layer; + return 0; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/heatmap_bucket.hpp b/src/mbgl/renderer/buckets/heatmap_bucket.hpp new file mode 100644 index 000000000..3b9f1edb8 --- /dev/null +++ b/src/mbgl/renderer/buckets/heatmap_bucket.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +class BucketParameters; + +class HeatmapBucket : public Bucket { +public: + HeatmapBucket(const BucketParameters&, const std::vector&); + + void addFeature(const GeometryTileFeature&, + const GeometryCollection&) override; + bool hasData() const override; + + void upload(gl::Context&) override; + + float getQueryRadius(const RenderLayer&) const override; + + gl::VertexVector vertices; + gl::IndexVector triangles; + SegmentVector segments; + + optional> vertexBuffer; + optional> indexBuffer; + + std::map paintPropertyBinders; + + const MapMode mode; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.cpp b/src/mbgl/renderer/buckets/hillshade_bucket.cpp index 8011681ee..00b953689 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 3d9f6c61a..5335f7ced 100644 --- a/src/mbgl/renderer/buckets/hillshade_bucket.hpp +++ b/src/mbgl/renderer/buckets/hillshade_bucket.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -16,8 +17,8 @@ namespace mbgl { class HillshadeBucket : public Bucket { public: - HillshadeBucket(PremultipliedImage&&); - HillshadeBucket(std::shared_ptr); + HillshadeBucket(PremultipliedImage&&, Tileset::DEMEncoding encoding); + HillshadeBucket(std::shared_ptr, 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 000000000..0f9e3239e --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -0,0 +1,178 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +using namespace style; + +RenderHeatmapLayer::RenderHeatmapLayer(Immutable _impl) + : RenderLayer(style::LayerType::Heatmap, _impl), + unevaluated(impl().paint.untransitioned()), colorRamp({256, 1}) { +} + +const style::HeatmapLayer::Impl& RenderHeatmapLayer::impl() const { + return static_cast(*baseImpl); +} + +std::unique_ptr RenderHeatmapLayer::createBucket(const BucketParameters& parameters, const std::vector& layers) const { + return std::make_unique(parameters, layers); +} + +void RenderHeatmapLayer::transition(const TransitionParameters& parameters) { + unevaluated = impl().paint.transitioned(parameters, std::move(unevaluated)); +} + +void RenderHeatmapLayer::evaluate(const PropertyEvaluationParameters& parameters) { + evaluated = unevaluated.evaluate(parameters); + + passes = (evaluated.get() > 0) + ? (RenderPass::Translucent | RenderPass::Pass3D) + : RenderPass::None; +} + +bool RenderHeatmapLayer::hasTransition() const { + return unevaluated.hasTransition(); +} + +void RenderHeatmapLayer::render(PaintParameters& parameters, RenderSource*) { + if (parameters.pass == RenderPass::Opaque) { + return; + } + + if (parameters.pass == RenderPass::Pass3D) { + const auto& viewportSize = parameters.staticData.backendSize; + const auto size = Size{viewportSize.width / 4, viewportSize.height / 4}; + + if (!renderTexture || renderTexture->getSize() != size) { + if (parameters.context.supportsHalfFloatTextures) { + renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::HalfFloat); + + try { + renderTexture->bind(); + } catch (const std::runtime_error& ex) { + // can't render to a half-float texture; falling back to unsigned byte one + renderTexture = nullopt; + parameters.context.supportsHalfFloatTextures = false; + } + } + + if (!renderTexture) { + renderTexture = OffscreenTexture(parameters.context, size, gl::TextureType::UnsignedByte); + renderTexture->bind(); + } + + } else { + renderTexture->bind(); + } + + if (!colorRampTexture) { + colorRampTexture = parameters.context.createTexture(colorRamp, 1, gl::TextureType::UnsignedByte); + } + + parameters.context.clear(Color{ 0.0f, 0.0f, 0.0f, 1.0f }, {}, {}); + + for (const RenderTile& tile : renderTiles) { + assert(dynamic_cast(tile.tile.getBucket(*baseImpl))); + HeatmapBucket& bucket = *reinterpret_cast(tile.tile.getBucket(*baseImpl)); + + const auto extrudeScale = tile.id.pixelsToTileUnits(1, parameters.state.getZoom()); + + const auto stencilMode = parameters.mapMode != MapMode::Continuous + ? parameters.stencilModeForClipping(tile.clip) + : gl::StencilMode::disabled(); + + parameters.programs.heatmap.get(evaluated).draw( + parameters.context, + gl::Triangles(), + parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly), + stencilMode, + gl::ColorMode::additive(), + HeatmapProgram::UniformValues { + uniforms::u_intensity::Value{evaluated.get()}, + uniforms::u_matrix::Value{tile.matrix}, + uniforms::heatmap::u_extrude_scale::Value{extrudeScale} + }, + *bucket.vertexBuffer, + *bucket.indexBuffer, + bucket.segments, + bucket.paintPropertyBinders.at(getID()), + evaluated, + parameters.state.getZoom(), + getID() + ); + } + + } else if (parameters.pass == RenderPass::Translucent) { + parameters.context.bindTexture(renderTexture->getTexture(), 0, gl::TextureFilter::Linear); + parameters.context.bindTexture(*colorRampTexture, 1, gl::TextureFilter::Linear); + + const auto& size = parameters.staticData.backendSize; + + mat4 viewportMat; + matrix::ortho(viewportMat, 0, size.width, size.height, 0, 0, 1); + + const Properties<>::PossiblyEvaluated properties; + + parameters.programs.heatmapTexture.draw( + parameters.context, gl::Triangles(), gl::DepthMode::disabled(), + gl::StencilMode::disabled(), parameters.colorModeForRenderPass(), + HeatmapTextureProgram::UniformValues{ + uniforms::u_matrix::Value{ viewportMat }, uniforms::u_world::Value{ size }, + uniforms::u_image::Value{ 0 }, + uniforms::u_color_ramp::Value{ 1 }, + uniforms::u_opacity::Value{ evaluated.get() } }, + parameters.staticData.extrusionTextureVertexBuffer, + parameters.staticData.quadTriangleIndexBuffer, + parameters.staticData.extrusionTextureSegments, + HeatmapTextureProgram::PaintPropertyBinders{ properties, 0 }, properties, + parameters.state.getZoom(), getID()); + } +} + +void RenderHeatmapLayer::updateColorRamp() { + auto colorValue = unevaluated.get().getValue(); + if (colorValue.isUndefined()) { + colorValue = HeatmapLayer::getDefaultHeatmapColor(); + } + + const auto length = colorRamp.bytes(); + + for (uint32_t i = 0; i < length; i += 4) { + const auto color = colorValue.evaluate(static_cast(i) / length); + colorRamp.data[i + 0] = std::floor(color.r * 255); + colorRamp.data[i + 1] = std::floor(color.g * 255); + colorRamp.data[i + 2] = std::floor(color.b * 255); + colorRamp.data[i + 3] = std::floor(color.a * 255); + } + + if (colorRampTexture) { + colorRampTexture = nullopt; + } +} + +bool RenderHeatmapLayer::queryIntersectsFeature( + const GeometryCoordinates& queryGeometry, + const GeometryTileFeature& feature, + const float zoom, + const float bearing, + const float pixelsToTileUnits) const { + (void) queryGeometry; + (void) feature; + (void) zoom; + (void) bearing; + (void) pixelsToTileUnits; + return false; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.hpp b/src/mbgl/renderer/layers/render_heatmap_layer.hpp new file mode 100644 index 000000000..3f0b1f91b --- /dev/null +++ b/src/mbgl/renderer/layers/render_heatmap_layer.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mbgl { + +class RenderHeatmapLayer: public RenderLayer { +public: + RenderHeatmapLayer(Immutable); + ~RenderHeatmapLayer() final = default; + + void transition(const TransitionParameters&) override; + void evaluate(const PropertyEvaluationParameters&) override; + bool hasTransition() const override; + void render(PaintParameters&, RenderSource*) override; + + bool queryIntersectsFeature( + const GeometryCoordinates&, + const GeometryTileFeature&, + const float, + const float, + const float) const override; + + void updateColorRamp(); + + std::unique_ptr createBucket(const BucketParameters&, const std::vector&) const override; + + // Paint properties + style::HeatmapPaintProperties::Unevaluated unevaluated; + style::HeatmapPaintProperties::PossiblyEvaluated evaluated; + + const style::HeatmapLayer::Impl& impl() const; + + PremultipliedImage colorRamp; + optional renderTexture; + optional colorRampTexture; +}; + +template <> +inline bool RenderLayer::is() const { + return type == style::LayerType::Heatmap; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index 7a767522c..bcfd4ffe9 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -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(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 e9b9db1ec..13093ee7e 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 createBucket(const BucketParameters&, const std::vector&) const override; diff --git a/src/mbgl/renderer/layers/render_symbol_layer.cpp b/src/mbgl/renderer/layers/render_symbol_layer.cpp index 04fcb2c3a..9e493003c 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 248905f83..bcdc175f1 100644 --- a/src/mbgl/renderer/render_layer.cpp +++ b/src/mbgl/renderer/render_layer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,8 @@ std::unique_ptr RenderLayer::create(Immutable impl) { return std::make_unique(staticImmutableCast(impl)); case LayerType::FillExtrusion: return std::make_unique(staticImmutableCast(impl)); + case LayerType::Heatmap: + return std::make_unique(staticImmutableCast(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp index db88230e5..53519c763 100644 --- a/src/mbgl/renderer/render_source.hpp +++ b/src/mbgl/renderer/render_source.hpp @@ -70,7 +70,7 @@ public: virtual std::vector 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 6d086c70b..1d2f2bb52 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 61e7d1724..2ac714e12 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +186,10 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { if (layerAdded || layerChanged) { layer.transition(transitionParameters); + + if (layer.is()) { + layer.as()->updateColorRamp(); + } } if (layerAdded || layerChanged || zoomChanged || layer.hasTransition()) { @@ -290,7 +295,11 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { RenderLayer* layer = getRenderLayer(layerImpl->id); assert(layer); - if (!parameters.staticData.has3D && (layer->is() || layer->is())) { + if (!parameters.staticData.has3D && ( + layer->is() || + layer->is() || + layer->is())) { + parameters.staticData.has3D = true; } @@ -722,12 +731,12 @@ std::vector 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 4f45d514a..4675ac79e 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -53,7 +53,7 @@ public: std::vector querySourceFeatures(const std::string& sourceID, const SourceQueryOptions&) const; std::vector 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 df615a7e2..057ad5a4a 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 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 82e691d5c..033d73102 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 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 8ea80cd81..cbf4db70b 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 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 55166ea90..72fccbd04 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 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 72cf4cea6..85ee0ace1 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 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 76716518d..b3153622c 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 baseImpl_, enabled = needsRendering; - optional tileset = impl().getTileset(); - - if (!tileset) { - return; - } - - if (tileURLTemplates != tileset->tiles) { - tileURLTemplates = tileset->tiles; + optional _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 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 b6b8bf977..741214a14 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 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> tileURLTemplates; + optional 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 e99cd040e..60b3fa9a3 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 baseImpl_, enabled = needsRendering; - optional tileset = impl().getTileset(); + optional _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 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 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 25041fde4..78eda199a 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 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> tileURLTemplates; + optional tileset; }; template <> diff --git a/src/mbgl/renderer/sources/render_vector_source.cpp b/src/mbgl/renderer/sources/render_vector_source.cpp index d53023e4d..e87bea5dc 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 baseImpl_, enabled = needsRendering; - optional tileset = impl().getTileset(); + optional _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 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 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 4a992e854..592160dc1 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 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> tileURLTemplates; + optional tileset; }; template <> diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 07239b7a1..c4372e711 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 ad3f91bf8..2638599c3 100644 --- a/src/mbgl/renderer/tile_pyramid.hpp +++ b/src/mbgl/renderer/tile_pyramid.hpp @@ -59,7 +59,7 @@ public: std::vector 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 f22058624..82ebbf05a 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 000000000..19927cfcc --- /dev/null +++ b/src/mbgl/shaders/heatmap.cpp @@ -0,0 +1,128 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include + +namespace mbgl { +namespace shaders { + +const char* heatmap::name = "heatmap"; +const char* heatmap::vertexSource = R"MBGL_SHADER( + +#ifndef HAS_UNIFORM_u_weight +uniform lowp float a_weight_t; +attribute highp vec2 a_weight; +varying highp float weight; +#else +uniform highp float u_weight; +#endif + + +#ifndef HAS_UNIFORM_u_radius +uniform lowp float a_radius_t; +attribute mediump vec2 a_radius; +#else +uniform mediump float u_radius; +#endif + + +uniform mat4 u_matrix; +uniform float u_extrude_scale; +uniform float u_opacity; +uniform float u_intensity; + +attribute vec2 a_pos; + +varying vec2 v_extrude; + +// Effective "0" in the kernel density texture to adjust the kernel size to; +// this empirically chosen number minimizes artifacts on overlapping kernels +// for typical heatmap cases (assuming clustered source) +const highp float ZERO = 1.0 / 255.0 / 16.0; + +// Gaussian kernel coefficient: 1 / sqrt(2 * PI) +#define GAUSS_COEF 0.3989422804014327 + +void main(void) { + +#ifndef HAS_UNIFORM_u_weight + weight = unpack_mix_vec2(a_weight, a_weight_t); +#else + highp float weight = u_weight; +#endif + + +#ifndef HAS_UNIFORM_u_radius + mediump float radius = unpack_mix_vec2(a_radius, a_radius_t); +#else + mediump float radius = u_radius; +#endif + + + // unencode the extrusion vector that we snuck into the a_pos vector + vec2 unscaled_extrude = vec2(mod(a_pos, 2.0) * 2.0 - 1.0); + + // This 'extrude' comes in ranging from [-1, -1], to [1, 1]. We'll use + // it to produce the vertices of a square mesh framing the point feature + // we're adding to the kernel density texture. We'll also pass it as + // a varying, so that the fragment shader can determine the distance of + // each fragment from the point feature. + // Before we do so, we need to scale it up sufficiently so that the + // kernel falls effectively to zero at the edge of the mesh. + // That is, we want to know S such that + // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO + // Which solves to: + // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 + float S = sqrt(-2.0 * log(ZERO / weight / u_intensity / GAUSS_COEF)) / 3.0; + + // Pass the varying in units of radius + v_extrude = S * unscaled_extrude; + + // Scale by radius and the zoom-based scale factor to produce actual + // mesh position + vec2 extrude = v_extrude * radius * u_extrude_scale; + + // multiply a_pos by 0.5, since we had it * 2 in order to sneak + // in extrusion data + vec4 pos = vec4(floor(a_pos * 0.5) + extrude, 0, 1); + + gl_Position = u_matrix * pos; +} + +)MBGL_SHADER"; +const char* heatmap::fragmentSource = R"MBGL_SHADER( + +#ifndef HAS_UNIFORM_u_weight +varying highp float weight; +#else +uniform highp float u_weight; +#endif + + +uniform highp float u_intensity; +varying vec2 v_extrude; + +// Gaussian kernel coefficient: 1 / sqrt(2 * PI) +#define GAUSS_COEF 0.3989422804014327 + +void main() { + +#ifdef HAS_UNIFORM_u_weight + highp float weight = u_weight; +#endif + + + // Kernel density estimation with a Gaussian kernel of size 5x5 + float d = -0.5 * 3.0 * 3.0 * dot(v_extrude, v_extrude); + float val = weight * u_intensity * GAUSS_COEF * exp(d); + + gl_FragColor = vec4(val, 1.0, 1.0, 1.0); + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(1.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap.hpp b/src/mbgl/shaders/heatmap.hpp new file mode 100644 index 000000000..a3c64db94 --- /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 000000000..c5d35c48a --- /dev/null +++ b/src/mbgl/shaders/heatmap_texture.cpp @@ -0,0 +1,42 @@ +// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED. + +#include + +namespace mbgl { +namespace shaders { + +const char* heatmap_texture::name = "heatmap_texture"; +const char* heatmap_texture::vertexSource = R"MBGL_SHADER( +uniform mat4 u_matrix; +uniform vec2 u_world; +attribute vec2 a_pos; +varying vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos * u_world, 0, 1); + + v_pos.x = a_pos.x; + v_pos.y = 1.0 - a_pos.y; +} + +)MBGL_SHADER"; +const char* heatmap_texture::fragmentSource = R"MBGL_SHADER( +uniform sampler2D u_image; +uniform sampler2D u_color_ramp; +uniform float u_opacity; +varying vec2 v_pos; + +void main() { + float t = texture2D(u_image, v_pos).r; + vec4 color = texture2D(u_color_ramp, vec2(t, 0.5)); + gl_FragColor = color * u_opacity; + +#ifdef OVERDRAW_INSPECTOR + gl_FragColor = vec4(0.0); +#endif +} + +)MBGL_SHADER"; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/heatmap_texture.hpp b/src/mbgl/shaders/heatmap_texture.hpp new file mode 100644 index 000000000..c51dc6b17 --- /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 733658435..8d0571f6a 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 6ed7af8aa..5d98b4e69 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 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 ad6998341..19472bc8d 100644 --- a/src/mbgl/style/conversion/layer.cpp +++ b/src/mbgl/style/conversion/layer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -165,6 +166,8 @@ optional> Converter>::operator()(c converted = convertVectorLayer(*id, value, error); } else if (*type == "raster") { converted = convertRasterLayer(*id, value, error); + } else if (*type == "heatmap") { + converted = convertVectorLayer(*id, value, error); } else if (*type == "hillshade") { converted = convertHillshadeLayer(*id, value, error); } else if (*type == "background") { diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp index adfcc4dd6..25c8fdb1c 100644 --- a/src/mbgl/style/conversion/make_property_setters.hpp +++ b/src/mbgl/style/conversion/make_property_setters.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,7 @@ inline auto makeLayoutPropertySetters() { + return result; } @@ -166,6 +168,17 @@ inline auto makePaintPropertySetters() { result["circle-stroke-opacity"] = &setProperty, &CircleLayer::setCircleStrokeOpacity>; result["circle-stroke-opacity-transition"] = &setTransition; + result["heatmap-radius"] = &setProperty, &HeatmapLayer::setHeatmapRadius>; + result["heatmap-radius-transition"] = &setTransition; + result["heatmap-weight"] = &setProperty, &HeatmapLayer::setHeatmapWeight>; + result["heatmap-weight-transition"] = &setTransition; + result["heatmap-intensity"] = &setProperty, &HeatmapLayer::setHeatmapIntensity>; + result["heatmap-intensity-transition"] = &setTransition; + result["heatmap-color"] = &setProperty; + result["heatmap-color-transition"] = &setTransition; + result["heatmap-opacity"] = &setProperty, &HeatmapLayer::setHeatmapOpacity>; + result["heatmap-opacity-transition"] = &setTransition; + result["fill-extrusion-opacity"] = &setProperty, &FillExtrusionLayer::setFillExtrusionOpacity>; result["fill-extrusion-opacity-transition"] = &setTransition; result["fill-extrusion-color"] = &setProperty, &FillExtrusionLayer::setFillExtrusionColor>; diff --git a/src/mbgl/style/conversion/property_setter.hpp b/src/mbgl/style/conversion/property_setter.hpp index 9e382b9c3..e3716a18d 100644 --- a/src/mbgl/style/conversion/property_setter.hpp +++ b/src/mbgl/style/conversion/property_setter.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/src/mbgl/style/conversion/tileset.cpp b/src/mbgl/style/conversion/tileset.cpp index 6e559c0ca..6d89cef94 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 Converter::operator()(const Convertible& value, Error& error) const { @@ -40,6 +40,16 @@ optional Converter::operator()(const Convertible& value, Error } } + auto encodingValue = objectMember(value, "encoding"); + if (encodingValue) { + optional 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 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 000000000..4989ff15f --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer.cpp @@ -0,0 +1,239 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include +#include +#include +// for constructing default heatmap-color ramp expression from style JSON +#include +#include +#include + +namespace mbgl { +namespace style { + +HeatmapLayer::HeatmapLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable(LayerType::Heatmap, layerID, sourceID)) { +} + +HeatmapLayer::HeatmapLayer(Immutable impl_) + : Layer(std::move(impl_)) { +} + +HeatmapLayer::~HeatmapLayer() = default; + +const HeatmapLayer::Impl& HeatmapLayer::impl() const { + return static_cast(*baseImpl); +} + +Mutable HeatmapLayer::mutableImpl() const { + return makeMutable(impl()); +} + +std::unique_ptr HeatmapLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = HeatmapPaintProperties::Transitionable(); + return std::make_unique(std::move(impl_)); +} + +void HeatmapLayer::Impl::stringifyLayout(rapidjson::Writer&) const { +} + +// Source + +const std::string& HeatmapLayer::getSourceID() const { + return impl().source; +} + +void HeatmapLayer::setSourceLayer(const std::string& sourceLayer) { + auto impl_ = mutableImpl(); + impl_->sourceLayer = sourceLayer; + baseImpl = std::move(impl_); +} + +const std::string& HeatmapLayer::getSourceLayer() const { + return impl().sourceLayer; +} + +// Filter + +void HeatmapLayer::setFilter(const Filter& filter) { + auto impl_ = mutableImpl(); + impl_->filter = filter; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +const Filter& HeatmapLayer::getFilter() const { + return impl().filter; +} + +// Visibility + +void HeatmapLayer::setVisibility(VisibilityType value) { + if (value == getVisibility()) + return; + auto impl_ = mutableImpl(); + impl_->visibility = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +// Zoom range + +void HeatmapLayer::setMinZoom(float minZoom) { + auto impl_ = mutableImpl(); + impl_->minZoom = minZoom; + baseImpl = std::move(impl_); +} + +void HeatmapLayer::setMaxZoom(float maxZoom) { + auto impl_ = mutableImpl(); + impl_->maxZoom = maxZoom; + baseImpl = std::move(impl_); +} + +// Layout properties + + +// Paint properties + +DataDrivenPropertyValue HeatmapLayer::getDefaultHeatmapRadius() { + return { 30 }; +} + +DataDrivenPropertyValue HeatmapLayer::getHeatmapRadius() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapRadius(DataDrivenPropertyValue value) { + if (value == getHeatmapRadius()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapRadiusTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapRadiusTransition() const { + return impl().paint.template get().options; +} + +DataDrivenPropertyValue HeatmapLayer::getDefaultHeatmapWeight() { + return { 1 }; +} + +DataDrivenPropertyValue HeatmapLayer::getHeatmapWeight() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapWeight(DataDrivenPropertyValue value) { + if (value == getHeatmapWeight()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapWeightTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapWeightTransition() const { + return impl().paint.template get().options; +} + +PropertyValue HeatmapLayer::getDefaultHeatmapIntensity() { + return { 1 }; +} + +PropertyValue HeatmapLayer::getHeatmapIntensity() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapIntensity(PropertyValue value) { + if (value == getHeatmapIntensity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapIntensityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapIntensityTransition() const { + return impl().paint.template get().options; +} + +HeatmapColorPropertyValue HeatmapLayer::getDefaultHeatmapColor() { + conversion::Error error; + std::string rawValue = R"JSON(["interpolate",["linear"],["heatmap-density"],0,"rgba(0, 0, 255, 0)",0.1,"royalblue",0.3,"cyan",0.5,"lime",0.7,"yellow",1,"red"])JSON"; + return *conversion::convertJSON(rawValue, error); +} + +HeatmapColorPropertyValue HeatmapLayer::getHeatmapColor() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapColor(HeatmapColorPropertyValue value) { + if (value == getHeatmapColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapColorTransition() const { + return impl().paint.template get().options; +} + +PropertyValue HeatmapLayer::getDefaultHeatmapOpacity() { + return { 1 }; +} + +PropertyValue HeatmapLayer::getHeatmapOpacity() const { + return impl().paint.template get().value; +} + +void HeatmapLayer::setHeatmapOpacity(PropertyValue value) { + if (value == getHeatmapOpacity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HeatmapLayer::setHeatmapOpacityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HeatmapLayer::getHeatmapOpacityTransition() const { + return impl().paint.template get().options; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.cpp b/src/mbgl/style/layers/heatmap_layer_impl.cpp new file mode 100644 index 000000000..af20888d9 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.cpp @@ -0,0 +1,15 @@ +#include + +namespace mbgl { +namespace style { + +bool HeatmapLayer::Impl::hasLayoutDifference(const Layer::Impl& other) const { + assert(dynamic_cast(&other)); + const auto& impl = static_cast(other); + return filter != impl.filter || + visibility != impl.visibility || + paint.hasDataDrivenPropertyDifference(impl.paint); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_impl.hpp b/src/mbgl/style/layers/heatmap_layer_impl.hpp new file mode 100644 index 000000000..cc27c3076 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_impl.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace style { + +class HeatmapLayer::Impl : public Layer::Impl { +public: + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer&) const override; + + HeatmapPaintProperties::Transitionable paint; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.cpp b/src/mbgl/style/layers/heatmap_layer_properties.cpp new file mode 100644 index 000000000..2edb83958 --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.cpp @@ -0,0 +1,9 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include + +namespace mbgl { +namespace style { + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/heatmap_layer_properties.hpp b/src/mbgl/style/layers/heatmap_layer_properties.hpp new file mode 100644 index 000000000..f7afa5fbe --- /dev/null +++ b/src/mbgl/style/layers/heatmap_layer_properties.hpp @@ -0,0 +1,40 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +struct HeatmapRadius : DataDrivenPaintProperty { + static float defaultValue() { return 30; } +}; + +struct HeatmapWeight : DataDrivenPaintProperty { + static float defaultValue() { return 1; } +}; + +struct HeatmapIntensity : PaintProperty { + static float defaultValue() { return 1; } +}; + +struct HeatmapOpacity : PaintProperty { + static float defaultValue() { return 1; } +}; + +class HeatmapPaintProperties : public Properties< + HeatmapRadius, + HeatmapWeight, + HeatmapIntensity, + HeatmapColor, + HeatmapOpacity +> {}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs index be44bb353..657a7f5a8 100644 --- a/src/mbgl/style/layers/layer.cpp.ejs +++ b/src/mbgl/style/layers/layer.cpp.ejs @@ -8,6 +8,12 @@ #include _layer.hpp> #include _layer_impl.hpp> #include +<% if (type === 'heatmap') { -%> +// for constructing default heatmap-color ramp expression from style JSON +#include +#include +#include +<% } -%> namespace mbgl { namespace style { @@ -134,7 +140,13 @@ void <%- camelize(type) %>Layer::set<%- camelize(property.name) %>(<%- propertyV // Paint properties <% for (const property of paintProperties) { %> <%- propertyValueType(property) %> <%- camelize(type) %>Layer::getDefault<%- camelize(property.name) %>() { +<% if (property.name === 'heatmap-color') { -%> + conversion::Error error; + std::string rawValue = R"JSON(<%- JSON.stringify(property.default) %>)JSON"; + return *conversion::convertJSON<<%- propertyValueType(property)%>>(rawValue, error); +<% } else { -%> return { <%- defaultValue(property) %> }; +<% } -%> } <%- propertyValueType(property) %> <%- camelize(type) %>Layer::get<%- camelize(property.name) %>() const { diff --git a/src/mbgl/style/layers/layer_properties.hpp.ejs b/src/mbgl/style/layers/layer_properties.hpp.ejs index cde1b80b7..1bceb8496 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 c4c996b3b..195eb645a 100644 --- a/src/mbgl/style/paint_property.hpp +++ b/src/mbgl/style/paint_property.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -48,5 +49,27 @@ public: static constexpr bool IsDataDriven = false; }; +/* + * Special-case paint property traits for heatmap-color, needed because + * heatmap-color values do not fit into the + * Undefined | Value | {Camera,Source,Composite}Function taxonomy that applies + * to all other paint properties. + * + * These traits are provided here--despite the fact that heatmap-color + * is not used like other paint properties--to allow the parameter-pack-based + * batch evaluation of paint properties to compile properly. + */ +class HeatmapColor { +public: + using TransitionableType = Transitionable; + using UnevaluatedType = Transitioning; + using EvaluatorType = PropertyEvaluator; + using PossiblyEvaluatedType = Color; + using Type = Color; + static constexpr bool IsDataDriven = false; + + static Color defaultValue() { return {}; } +}; + } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp index f2a9c0f22..d330b3120 100644 --- a/src/mbgl/style/style_impl.cpp +++ b/src/mbgl/style/style_impl.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index 400fe79ac..fc24c7890 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -230,6 +230,8 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& if (bucket.hasCollisionBoxData()) bucket.collisionBox.dynamicVertices.clear(); if (bucket.hasCollisionCircleData()) bucket.collisionCircle.dynamicVertices.clear(); + JointOpacityState duplicateOpacityState(false, false, true); + JointOpacityState defaultOpacityState( bucket.layout.get(), bucket.layout.get(), @@ -239,9 +241,12 @@ void Placement::updateBucketOpacities(SymbolBucket& bucket, std::set& 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 7c8a85c4a..33962ad87 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 b270378ec..5db298cf4 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(*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 modified_, optional data) { pending = true; ++correlationID; - worker.invoke(&RasterDEMTileWorker::parse, data, correlationID); + worker.invoke(&RasterDEMTileWorker::parse, data, correlationID, encoding); } void RasterDEMTile::onParsed(std::unique_ptr result, const uint64_t resultCorrelationID) { diff --git a/src/mbgl/tile/raster_dem_tile.hpp b/src/mbgl/tile/raster_dem_tile.hpp index 68f8a91e0..0c8dd7596 100644 --- a/src/mbgl/tile/raster_dem_tile.hpp +++ b/src/mbgl/tile/raster_dem_tile.hpp @@ -94,6 +94,7 @@ private: Actor 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 ed8573788..7338e578c 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, ActorRef : parent(std::move(parent_)) { } -void RasterDEMTileWorker::parse(std::shared_ptr data, uint64_t correlationID) { +void RasterDEMTileWorker::parse(std::shared_ptr 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(decodeImage(*data)); + auto bucket = std::make_unique(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 14fd1f43b..5a8222bc2 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 +#include #include #include @@ -13,7 +14,7 @@ class RasterDEMTileWorker { public: RasterDEMTileWorker(ActorRef, ActorRef); - void parse(std::shared_ptr data, uint64_t correlationID); + void parse(std::shared_ptr data, uint64_t correlationID, Tileset::DEMEncoding encoding); private: ActorRef parent; diff --git a/src/mbgl/util/offscreen_texture.cpp b/src/mbgl/util/offscreen_texture.cpp index 339e74b25..03f555eae 100644 --- a/src/mbgl/util/offscreen_texture.cpp +++ b/src/mbgl/util/offscreen_texture.cpp @@ -11,20 +11,21 @@ OffscreenTexture& OffscreenTexture::operator=(OffscreenTexture&&) = default; class OffscreenTexture::Impl { public: - Impl(gl::Context& context_, const Size size_) - : context(context_), size(std::move(size_)) { + Impl(gl::Context& context_, const Size size_, const gl::TextureType type_) + : context(context_), size(std::move(size_)), type(type_) { assert(!size.isEmpty()); } Impl(gl::Context& context_, const Size size_, - gl::Renderbuffer& depth_) - : context(context_), size(std::move(size_)), depth(&depth_) { + gl::Renderbuffer& depth_, + const gl::TextureType type_) + : context(context_), size(std::move(size_)), depth(&depth_), type(type_) { assert(!size.isEmpty()); } void bind() { if (!framebuffer) { - texture = context.createTexture(size, gl::TextureFormat::RGBA); + texture = context.createTexture(size, gl::TextureFormat::RGBA, 0, type); if (depth) { framebuffer = context.createFramebuffer(*texture, *depth); } else { @@ -58,18 +59,21 @@ private: optional framebuffer; optional texture; gl::Renderbuffer* depth = nullptr; + const gl::TextureType type; }; OffscreenTexture::OffscreenTexture(gl::Context& context, - const Size size) - : impl(std::make_unique(context, std::move(size))) { + const Size size, + const gl::TextureType type) + : impl(std::make_unique(context, std::move(size), type)) { assert(!size.isEmpty()); } OffscreenTexture::OffscreenTexture(gl::Context& context, const Size size, - gl::Renderbuffer& renderbuffer) - : impl(std::make_unique(context, std::move(size), renderbuffer)) { + gl::Renderbuffer& renderbuffer, + const gl::TextureType type) + : impl(std::make_unique(context, std::move(size), renderbuffer, type)) { assert(!size.isEmpty()); } diff --git a/src/mbgl/util/offscreen_texture.hpp b/src/mbgl/util/offscreen_texture.hpp index 7f7e0f033..36f24f16d 100644 --- a/src/mbgl/util/offscreen_texture.hpp +++ b/src/mbgl/util/offscreen_texture.hpp @@ -12,10 +12,12 @@ class Texture; class OffscreenTexture { public: OffscreenTexture(gl::Context&, - Size size = { 256, 256 }); + Size size = { 256, 256 }, + gl::TextureType type = gl::TextureType::UnsignedByte); OffscreenTexture(gl::Context&, Size size, - gl::Renderbuffer&); + gl::Renderbuffer&, + gl::TextureType type = gl::TextureType::UnsignedByte); ~OffscreenTexture(); OffscreenTexture(OffscreenTexture&&); OffscreenTexture& operator=(OffscreenTexture&&); diff --git a/test/geometry/dem_data.test.cpp b/test/geometry/dem_data.test.cpp index 30091973b..3848f028f 100644 --- a/test/geometry/dem_data.test.cpp +++ b/test/geometry/dem_data.test.cpp @@ -1,6 +1,7 @@ #include #include +#include #include 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); + DEMData demdata(image, Tileset::DEMEncoding::Mapbox); - 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); + 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 a39d2963d..978a41a30 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 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 4d509e6c7..e1756f8e7 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 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 8002cd038..9487277cc 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 converted = convertJSON(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(R"JSON({ diff --git a/test/tile/raster_dem_tile.test.cpp b/test/tile/raster_dem_tile.test.cpp index c6f9b80b4..5a6f5a8c9 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(PremultipliedImage({16, 16})), 0); + tile.onParsed(std::make_unique(PremultipliedImage({16, 16}), Tileset::DEMEncoding::Mapbox), 0); EXPECT_TRUE(tile.isRenderable()); EXPECT_TRUE(tile.isLoaded()); EXPECT_TRUE(tile.isComplete());