summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/core-files.cmake28
-rw-r--r--cmake/test-files.cmake4
-rw-r--r--include/mbgl/style/layer.hpp3
-rw-r--r--include/mbgl/style/layer_type.hpp3
-rw-r--r--include/mbgl/style/layers/hillshade_layer.hpp86
-rw-r--r--include/mbgl/style/layers/layer.hpp.ejs2
-rw-r--r--include/mbgl/style/source.hpp1
-rw-r--r--include/mbgl/style/sources/raster_dem_source.hpp25
-rw-r--r--include/mbgl/style/sources/raster_source.hpp4
-rw-r--r--include/mbgl/style/types.hpp6
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java308
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java21
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/PropertyFactory.java228
-rw-r--r--platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs2
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HillshadeLayerTest.java523
-rw-r--r--platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs2
-rw-r--r--platform/android/config.cmake2
-rw-r--r--platform/android/src/style/conversion/types.hpp7
-rw-r--r--platform/android/src/style/conversion/types_string_values.hpp14
-rw-r--r--platform/android/src/style/layers/hillshade_layer.cpp178
-rw-r--r--platform/android/src/style/layers/hillshade_layer.hpp58
-rw-r--r--platform/android/src/style/layers/layer.cpp4
-rw-r--r--platform/android/src/style/layers/layers.cpp4
-rw-r--r--platform/darwin/src/MGLHillshadeStyleLayer.h276
-rw-r--r--platform/darwin/src/MGLHillshadeStyleLayer.mm286
-rw-r--r--platform/darwin/test/MGLHillshadeStyleLayerTests.mm345
-rw-r--r--platform/default/mbgl/storage/offline_download.cpp13
-rw-r--r--platform/ios/docs/guides/For Style Authors.md1
-rw-r--r--platform/macos/docs/guides/For Style Authors.md1
-rw-r--r--platform/node/src/node_map.cpp5
-rw-r--r--platform/node/test/ignores.json32
-rw-r--r--src/mbgl/geometry/dem_data.cpp94
-rw-r--r--src/mbgl/geometry/dem_data.hpp48
-rw-r--r--src/mbgl/programs/hillshade_prepare_program.cpp7
-rw-r--r--src/mbgl/programs/hillshade_prepare_program.hpp47
-rw-r--r--src/mbgl/programs/hillshade_program.cpp7
-rw-r--r--src/mbgl/programs/hillshade_program.hpp55
-rw-r--r--src/mbgl/programs/programs.hpp6
-rw-r--r--src/mbgl/renderer/buckets/hillshade_bucket.cpp113
-rw-r--r--src/mbgl/renderer/buckets/hillshade_bucket.hpp58
-rw-r--r--src/mbgl/renderer/layers/render_hillshade_layer.cpp158
-rw-r--r--src/mbgl/renderer/layers/render_hillshade_layer.hpp38
-rw-r--r--src/mbgl/renderer/render_layer.cpp3
-rw-r--r--src/mbgl/renderer/render_source.cpp3
-rw-r--r--src/mbgl/renderer/render_source.hpp2
-rw-r--r--src/mbgl/renderer/renderer_impl.cpp3
-rw-r--r--src/mbgl/renderer/sources/render_raster_dem_source.cpp165
-rw-r--r--src/mbgl/renderer/sources/render_raster_dem_source.hpp54
-rw-r--r--src/mbgl/renderer/tile_pyramid.cpp7
-rw-r--r--src/mbgl/renderer/tile_pyramid.hpp1
-rw-r--r--src/mbgl/shaders/hillshade.cpp80
-rw-r--r--src/mbgl/shaders/hillshade.hpp16
-rw-r--r--src/mbgl/shaders/hillshade_prepare.cpp99
-rw-r--r--src/mbgl/shaders/hillshade_prepare.hpp16
-rw-r--r--src/mbgl/style/conversion/layer.cpp20
-rw-r--r--src/mbgl/style/conversion/make_property_setters.hpp15
-rw-r--r--src/mbgl/style/conversion/source.cpp27
-rw-r--r--src/mbgl/style/expression/value.cpp4
-rw-r--r--src/mbgl/style/layers/hillshade_layer.cpp238
-rw-r--r--src/mbgl/style/layers/hillshade_layer_impl.cpp11
-rw-r--r--src/mbgl/style/layers/hillshade_layer_impl.hpp21
-rw-r--r--src/mbgl/style/layers/hillshade_layer_properties.cpp9
-rw-r--r--src/mbgl/style/layers/hillshade_layer_properties.hpp49
-rw-r--r--src/mbgl/style/layers/layer.cpp.ejs2
-rw-r--r--src/mbgl/style/sources/raster_dem_source.cpp19
-rw-r--r--src/mbgl/style/sources/raster_source.cpp4
-rw-r--r--src/mbgl/style/sources/raster_source_impl.cpp4
-rw-r--r--src/mbgl/style/sources/raster_source_impl.hpp2
-rw-r--r--src/mbgl/style/style_impl.cpp1
-rw-r--r--src/mbgl/style/types.cpp5
-rw-r--r--src/mbgl/tile/raster_dem_tile.cpp124
-rw-r--r--src/mbgl/tile/raster_dem_tile.hpp104
-rw-r--r--src/mbgl/tile/raster_dem_tile_worker.cpp27
-rw-r--r--src/mbgl/tile/raster_dem_tile_worker.hpp22
-rw-r--r--src/mbgl/tile/raster_tile.cpp3
-rw-r--r--src/mbgl/tile/raster_tile.hpp4
-rw-r--r--src/mbgl/tile/tile.cpp3
-rw-r--r--src/mbgl/tile/tile.hpp2
-rw-r--r--src/mbgl/tile/tile_cache.cpp13
-rw-r--r--src/mbgl/tile/tile_cache.hpp3
-rw-r--r--src/mbgl/util/mapbox.cpp2
-rw-r--r--test/geometry/dem_data.test.cpp140
-rw-r--r--test/style/source.test.cpp194
-rw-r--r--test/tile/raster_dem_tile.test.cpp93
84 files changed, 4661 insertions, 56 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake
index 8866932fc4..8148fcdc88 100644
--- a/cmake/core-files.cmake
+++ b/cmake/core-files.cmake
@@ -44,6 +44,8 @@ set(MBGL_CORE_FILES
# geometry
src/mbgl/geometry/anchor.hpp
src/mbgl/geometry/debug_font_data.hpp
+ src/mbgl/geometry/dem_data.cpp
+ src/mbgl/geometry/dem_data.hpp
src/mbgl/geometry/feature_index.cpp
src/mbgl/geometry/feature_index.hpp
src/mbgl/geometry/line_atlas.cpp
@@ -144,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/hillshade_prepare_program.cpp
+ src/mbgl/programs/hillshade_prepare_program.hpp
+ src/mbgl/programs/hillshade_program.cpp
+ src/mbgl/programs/hillshade_program.hpp
src/mbgl/programs/line_program.cpp
src/mbgl/programs/line_program.hpp
src/mbgl/programs/program.hpp
@@ -219,6 +225,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/hillshade_bucket.cpp
+ src/mbgl/renderer/buckets/hillshade_bucket.hpp
src/mbgl/renderer/buckets/line_bucket.cpp
src/mbgl/renderer/buckets/line_bucket.hpp
src/mbgl/renderer/buckets/raster_bucket.cpp
@@ -237,6 +245,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_hillshade_layer.cpp
+ src/mbgl/renderer/layers/render_hillshade_layer.hpp
src/mbgl/renderer/layers/render_line_layer.cpp
src/mbgl/renderer/layers/render_line_layer.hpp
src/mbgl/renderer/layers/render_raster_layer.cpp
@@ -251,6 +261,8 @@ set(MBGL_CORE_FILES
src/mbgl/renderer/sources/render_geojson_source.hpp
src/mbgl/renderer/sources/render_image_source.cpp
src/mbgl/renderer/sources/render_image_source.hpp
+ src/mbgl/renderer/sources/render_raster_dem_source.cpp
+ src/mbgl/renderer/sources/render_raster_dem_source.hpp
src/mbgl/renderer/sources/render_raster_source.cpp
src/mbgl/renderer/sources/render_raster_source.hpp
src/mbgl/renderer/sources/render_vector_source.cpp
@@ -285,6 +297,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/hillshade.cpp
+ src/mbgl/shaders/hillshade.hpp
+ src/mbgl/shaders/hillshade_prepare.cpp
+ src/mbgl/shaders/hillshade_prepare.hpp
src/mbgl/shaders/line.cpp
src/mbgl/shaders/line.hpp
src/mbgl/shaders/line_pattern.cpp
@@ -479,6 +495,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/hillshade_layer.hpp
include/mbgl/style/layers/line_layer.hpp
include/mbgl/style/layers/raster_layer.hpp
include/mbgl/style/layers/symbol_layer.hpp
@@ -505,6 +522,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/hillshade_layer.cpp
+ src/mbgl/style/layers/hillshade_layer_impl.cpp
+ src/mbgl/style/layers/hillshade_layer_impl.hpp
+ src/mbgl/style/layers/hillshade_layer_properties.cpp
+ src/mbgl/style/layers/hillshade_layer_properties.hpp
src/mbgl/style/layers/line_layer.cpp
src/mbgl/style/layers/line_layer_impl.cpp
src/mbgl/style/layers/line_layer_impl.hpp
@@ -525,6 +547,7 @@ set(MBGL_CORE_FILES
include/mbgl/style/sources/custom_geometry_source.hpp
include/mbgl/style/sources/geojson_source.hpp
include/mbgl/style/sources/image_source.hpp
+ include/mbgl/style/sources/raster_dem_source.hpp
include/mbgl/style/sources/raster_source.hpp
include/mbgl/style/sources/vector_source.hpp
src/mbgl/style/sources/custom_geometry_source.cpp
@@ -536,6 +559,7 @@ set(MBGL_CORE_FILES
src/mbgl/style/sources/image_source.cpp
src/mbgl/style/sources/image_source_impl.cpp
src/mbgl/style/sources/image_source_impl.hpp
+ src/mbgl/style/sources/raster_dem_source.cpp
src/mbgl/style/sources/raster_source.cpp
src/mbgl/style/sources/raster_source_impl.cpp
src/mbgl/style/sources/raster_source_impl.hpp
@@ -587,6 +611,10 @@ set(MBGL_CORE_FILES
src/mbgl/tile/geometry_tile_data.hpp
src/mbgl/tile/geometry_tile_worker.cpp
src/mbgl/tile/geometry_tile_worker.hpp
+ src/mbgl/tile/raster_dem_tile.cpp
+ src/mbgl/tile/raster_dem_tile.hpp
+ src/mbgl/tile/raster_dem_tile_worker.cpp
+ src/mbgl/tile/raster_dem_tile_worker.hpp
src/mbgl/tile/raster_tile.cpp
src/mbgl/tile/raster_tile.hpp
src/mbgl/tile/raster_tile_worker.cpp
diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake
index 381c609719..73f1546308 100644
--- a/cmake/test-files.cmake
+++ b/cmake/test-files.cmake
@@ -21,6 +21,9 @@ set(MBGL_TEST_FILES
test/api/recycle_map.cpp
test/api/zoom_history.cpp
+ # geometry
+ test/geometry/dem_data.test.cpp
+
# gl
test/gl/bucket.test.cpp
test/gl/context.test.cpp
@@ -123,6 +126,7 @@ set(MBGL_TEST_FILES
test/tile/custom_geometry_tile.test.cpp
test/tile/geojson_tile.test.cpp
test/tile/geometry_tile_data.test.cpp
+ test/tile/raster_dem_tile.test.cpp
test/tile/raster_tile.test.cpp
test/tile/tile_coordinate.test.cpp
test/tile/tile_id.test.cpp
diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp
index eb2dbf830b..8a5a4236b3 100644
--- a/include/mbgl/style/layer.hpp
+++ b/include/mbgl/style/layer.hpp
@@ -19,6 +19,7 @@ class LineLayer;
class CircleLayer;
class SymbolLayer;
class RasterLayer;
+class HillshadeLayer;
class BackgroundLayer;
class CustomLayer;
class FillExtrusionLayer;
@@ -86,6 +87,8 @@ public:
return std::forward<V>(visitor)(*as<RasterLayer>());
case LayerType::Background:
return std::forward<V>(visitor)(*as<BackgroundLayer>());
+ case LayerType::Hillshade:
+ return std::forward<V>(visitor)(*as<HillshadeLayer>());
case LayerType::Custom:
return std::forward<V>(visitor)(*as<CustomLayer>());
case LayerType::FillExtrusion:
diff --git a/include/mbgl/style/layer_type.hpp b/include/mbgl/style/layer_type.hpp
index 66ff834eee..951757134b 100644
--- a/include/mbgl/style/layer_type.hpp
+++ b/include/mbgl/style/layer_type.hpp
@@ -9,10 +9,11 @@ enum class LayerType {
Circle,
Symbol,
Raster,
+ Hillshade,
Background,
Custom,
FillExtrusion,
};
} // namespace style
-} // namespace mbgl \ No newline at end of file
+} // namespace mbgl
diff --git a/include/mbgl/style/layers/hillshade_layer.hpp b/include/mbgl/style/layers/hillshade_layer.hpp
new file mode 100644
index 0000000000..35664da46c
--- /dev/null
+++ b/include/mbgl/style/layers/hillshade_layer.hpp
@@ -0,0 +1,86 @@
+// This file is generated. Do not edit.
+
+#pragma once
+
+#include <mbgl/style/layer.hpp>
+#include <mbgl/style/filter.hpp>
+#include <mbgl/style/property_value.hpp>
+#include <mbgl/style/data_driven_property_value.hpp>
+
+#include <mbgl/util/color.hpp>
+
+namespace mbgl {
+namespace style {
+
+class TransitionOptions;
+
+class HillshadeLayer : public Layer {
+public:
+ HillshadeLayer(const std::string& layerID, const std::string& sourceID);
+ ~HillshadeLayer() final;
+
+ // Source
+ const std::string& getSourceID() const;
+
+ // Visibility
+ void setVisibility(VisibilityType) final;
+
+ // Zoom range
+ void setMinZoom(float) final;
+ void setMaxZoom(float) final;
+
+ // Paint properties
+
+ static PropertyValue<float> getDefaultHillshadeIlluminationDirection();
+ PropertyValue<float> getHillshadeIlluminationDirection() const;
+ void setHillshadeIlluminationDirection(PropertyValue<float>);
+ void setHillshadeIlluminationDirectionTransition(const TransitionOptions&);
+ TransitionOptions getHillshadeIlluminationDirectionTransition() const;
+
+ static PropertyValue<HillshadeIlluminationAnchorType> getDefaultHillshadeIlluminationAnchor();
+ PropertyValue<HillshadeIlluminationAnchorType> getHillshadeIlluminationAnchor() const;
+ void setHillshadeIlluminationAnchor(PropertyValue<HillshadeIlluminationAnchorType>);
+ void setHillshadeIlluminationAnchorTransition(const TransitionOptions&);
+ TransitionOptions getHillshadeIlluminationAnchorTransition() const;
+
+ static PropertyValue<float> getDefaultHillshadeExaggeration();
+ PropertyValue<float> getHillshadeExaggeration() const;
+ void setHillshadeExaggeration(PropertyValue<float>);
+ void setHillshadeExaggerationTransition(const TransitionOptions&);
+ TransitionOptions getHillshadeExaggerationTransition() const;
+
+ static PropertyValue<Color> getDefaultHillshadeShadowColor();
+ PropertyValue<Color> getHillshadeShadowColor() const;
+ void setHillshadeShadowColor(PropertyValue<Color>);
+ void setHillshadeShadowColorTransition(const TransitionOptions&);
+ TransitionOptions getHillshadeShadowColorTransition() const;
+
+ static PropertyValue<Color> getDefaultHillshadeHighlightColor();
+ PropertyValue<Color> getHillshadeHighlightColor() const;
+ void setHillshadeHighlightColor(PropertyValue<Color>);
+ void setHillshadeHighlightColorTransition(const TransitionOptions&);
+ TransitionOptions getHillshadeHighlightColorTransition() const;
+
+ static PropertyValue<Color> getDefaultHillshadeAccentColor();
+ PropertyValue<Color> getHillshadeAccentColor() const;
+ void setHillshadeAccentColor(PropertyValue<Color>);
+ void setHillshadeAccentColorTransition(const TransitionOptions&);
+ TransitionOptions getHillshadeAccentColorTransition() const;
+
+ // Private implementation
+
+ class Impl;
+ const Impl& impl() const;
+
+ Mutable<Impl> mutableImpl() const;
+ HillshadeLayer(Immutable<Impl>);
+ std::unique_ptr<Layer> cloneRef(const std::string& id) const final;
+};
+
+template <>
+inline bool Layer::is<HillshadeLayer>() const {
+ return getType() == LayerType::Hillshade;
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs
index 4ee5545247..265dd57e1f 100644
--- a/include/mbgl/style/layers/layer.hpp.ejs
+++ b/include/mbgl/style/layers/layer.hpp.ejs
@@ -35,7 +35,7 @@ public:
<% if (type !== 'background') { -%>
// Source
const std::string& getSourceID() const;
-<% if (type !== 'raster') { -%>
+<% if (type !== 'raster' && type !== 'hillshade') { -%>
const std::string& getSourceLayer() const;
void setSourceLayer(const std::string& sourceLayer);
diff --git a/include/mbgl/style/source.hpp b/include/mbgl/style/source.hpp
index 0b6a6c72d9..2f2838ade8 100644
--- a/include/mbgl/style/source.hpp
+++ b/include/mbgl/style/source.hpp
@@ -17,6 +17,7 @@ namespace style {
class VectorSource;
class RasterSource;
+class RasterDEMSource;
class GeoJSONSource;
class SourceObserver;
diff --git a/include/mbgl/style/sources/raster_dem_source.hpp b/include/mbgl/style/sources/raster_dem_source.hpp
new file mode 100644
index 0000000000..82588613bc
--- /dev/null
+++ b/include/mbgl/style/sources/raster_dem_source.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <mbgl/style/sources/raster_source.hpp>
+#include <mbgl/util/tileset.hpp>
+#include <mbgl/util/variant.hpp>
+
+namespace mbgl {
+
+class AsyncRequest;
+
+namespace style {
+
+class RasterDEMSource : public RasterSource {
+public:
+ RasterDEMSource(std::string id, variant<std::string, Tileset> urlOrTileset, uint16_t tileSize);
+
+};
+
+template <>
+inline bool Source::is<RasterDEMSource>() const {
+ return getType() == SourceType::RasterDEM;
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/include/mbgl/style/sources/raster_source.hpp b/include/mbgl/style/sources/raster_source.hpp
index 7f23a7ca4b..5aa81aa979 100644
--- a/include/mbgl/style/sources/raster_source.hpp
+++ b/include/mbgl/style/sources/raster_source.hpp
@@ -12,8 +12,8 @@ namespace style {
class RasterSource : public Source {
public:
- RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset, uint16_t tileSize);
- ~RasterSource() final;
+ RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset, uint16_t tileSize, SourceType sourceType = SourceType::Raster);
+ ~RasterSource() override;
const variant<std::string, Tileset>& getURLOrTileset() const;
optional<std::string> getURL() const;
diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp
index 6fe457e181..693972a72f 100644
--- a/include/mbgl/style/types.hpp
+++ b/include/mbgl/style/types.hpp
@@ -10,6 +10,7 @@ namespace style {
enum class SourceType : uint8_t {
Vector,
Raster,
+ RasterDEM,
GeoJSON,
Video,
Annotations,
@@ -37,6 +38,11 @@ enum class LineJoinType : uint8_t {
FlipBevel
};
+enum class HillshadeIlluminationAnchorType : bool {
+ Map,
+ Viewport
+};
+
enum class TranslateAnchorType : bool {
Map,
Viewport
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
new file mode 100644
index 0000000000..be0df6c145
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/HillshadeLayer.java
@@ -0,0 +1,308 @@
+// 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;
+
+/**
+ * Client-side hillshading visualization based on DEM data. Currently, the implementation only supports Mapbox Terrain RGB tiles
+ *
+ * @see <a href="https://www.mapbox.com/mapbox-gl-style-spec/#layers-hillshade">The online documentation</a>
+ */
+@UiThread
+public class HillshadeLayer extends Layer {
+
+ /**
+ * Creates a HillshadeLayer.
+ *
+ * @param nativePtr pointer used by core
+ */
+ public HillshadeLayer(long nativePtr) {
+ super(nativePtr);
+ }
+
+ /**
+ * Creates a HillshadeLayer.
+ *
+ * @param layerId the id of the layer
+ * @param sourceId the id of the source
+ */
+ public HillshadeLayer(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 HillshadeLayer withSourceLayer(String sourceLayer) {
+ setSourceLayer(sourceLayer);
+ return this;
+ }
+
+ /**
+ * Set a property or properties.
+ *
+ * @param properties the var-args properties
+ * @return This
+ */
+ public HillshadeLayer withProperties(@NonNull PropertyValue<?>... properties) {
+ setProperties(properties);
+ return this;
+ }
+
+ // Property getters
+
+ /**
+ * Get the HillshadeIlluminationDirection property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Float> getHillshadeIlluminationDirection() {
+ return (PropertyValue<Float>) new PropertyValue("hillshade-illumination-direction", nativeGetHillshadeIlluminationDirection());
+ }
+
+ /**
+ * Get the HillshadeIlluminationDirection property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHillshadeIlluminationDirectionTransition() {
+ return nativeGetHillshadeIlluminationDirectionTransition();
+ }
+
+ /**
+ * Set the HillshadeIlluminationDirection property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHillshadeIlluminationDirectionTransition(TransitionOptions options) {
+ nativeSetHillshadeIlluminationDirectionTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HillshadeIlluminationAnchor property
+ *
+ * @return property wrapper value around String
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<String> getHillshadeIlluminationAnchor() {
+ return (PropertyValue<String>) new PropertyValue("hillshade-illumination-anchor", nativeGetHillshadeIlluminationAnchor());
+ }
+
+ /**
+ * Get the HillshadeExaggeration property
+ *
+ * @return property wrapper value around Float
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<Float> getHillshadeExaggeration() {
+ return (PropertyValue<Float>) new PropertyValue("hillshade-exaggeration", nativeGetHillshadeExaggeration());
+ }
+
+ /**
+ * Get the HillshadeExaggeration property transition options
+ *
+ * @return transition options for Float
+ */
+ public TransitionOptions getHillshadeExaggerationTransition() {
+ return nativeGetHillshadeExaggerationTransition();
+ }
+
+ /**
+ * Set the HillshadeExaggeration property transition options
+ *
+ * @param options transition options for Float
+ */
+ public void setHillshadeExaggerationTransition(TransitionOptions options) {
+ nativeSetHillshadeExaggerationTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HillshadeShadowColor property
+ *
+ * @return property wrapper value around String
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<String> getHillshadeShadowColor() {
+ return (PropertyValue<String>) new PropertyValue("hillshade-shadow-color", nativeGetHillshadeShadowColor());
+ }
+
+ /**
+ * The shading color of areas that face away from the light source.
+ *
+ * @return int representation of a rgba string color
+ * @throws RuntimeException thrown if property isn't a value
+ */
+ @ColorInt
+ public int getHillshadeShadowColorAsInt() {
+ PropertyValue<String> value = getHillshadeShadowColor();
+ if (value.isValue()) {
+ return rgbaToColor(value.getValue());
+ } else {
+ throw new RuntimeException("hillshade-shadow-color was set as a Function");
+ }
+ }
+
+ /**
+ * Get the HillshadeShadowColor property transition options
+ *
+ * @return transition options for String
+ */
+ public TransitionOptions getHillshadeShadowColorTransition() {
+ return nativeGetHillshadeShadowColorTransition();
+ }
+
+ /**
+ * Set the HillshadeShadowColor property transition options
+ *
+ * @param options transition options for String
+ */
+ public void setHillshadeShadowColorTransition(TransitionOptions options) {
+ nativeSetHillshadeShadowColorTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HillshadeHighlightColor property
+ *
+ * @return property wrapper value around String
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<String> getHillshadeHighlightColor() {
+ return (PropertyValue<String>) new PropertyValue("hillshade-highlight-color", nativeGetHillshadeHighlightColor());
+ }
+
+ /**
+ * The shading color of areas that faces towards the light source.
+ *
+ * @return int representation of a rgba string color
+ * @throws RuntimeException thrown if property isn't a value
+ */
+ @ColorInt
+ public int getHillshadeHighlightColorAsInt() {
+ PropertyValue<String> value = getHillshadeHighlightColor();
+ if (value.isValue()) {
+ return rgbaToColor(value.getValue());
+ } else {
+ throw new RuntimeException("hillshade-highlight-color was set as a Function");
+ }
+ }
+
+ /**
+ * Get the HillshadeHighlightColor property transition options
+ *
+ * @return transition options for String
+ */
+ public TransitionOptions getHillshadeHighlightColorTransition() {
+ return nativeGetHillshadeHighlightColorTransition();
+ }
+
+ /**
+ * Set the HillshadeHighlightColor property transition options
+ *
+ * @param options transition options for String
+ */
+ public void setHillshadeHighlightColorTransition(TransitionOptions options) {
+ nativeSetHillshadeHighlightColorTransition(options.getDuration(), options.getDelay());
+ }
+
+ /**
+ * Get the HillshadeAccentColor property
+ *
+ * @return property wrapper value around String
+ */
+ @SuppressWarnings("unchecked")
+ public PropertyValue<String> getHillshadeAccentColor() {
+ return (PropertyValue<String>) new PropertyValue("hillshade-accent-color", nativeGetHillshadeAccentColor());
+ }
+
+ /**
+ * The shading color used to accentuate rugged terrain like sharp cliffs and gorges.
+ *
+ * @return int representation of a rgba string color
+ * @throws RuntimeException thrown if property isn't a value
+ */
+ @ColorInt
+ public int getHillshadeAccentColorAsInt() {
+ PropertyValue<String> value = getHillshadeAccentColor();
+ if (value.isValue()) {
+ return rgbaToColor(value.getValue());
+ } else {
+ throw new RuntimeException("hillshade-accent-color was set as a Function");
+ }
+ }
+
+ /**
+ * Get the HillshadeAccentColor property transition options
+ *
+ * @return transition options for String
+ */
+ public TransitionOptions getHillshadeAccentColorTransition() {
+ return nativeGetHillshadeAccentColorTransition();
+ }
+
+ /**
+ * Set the HillshadeAccentColor property transition options
+ *
+ * @param options transition options for String
+ */
+ public void setHillshadeAccentColorTransition(TransitionOptions options) {
+ nativeSetHillshadeAccentColorTransition(options.getDuration(), options.getDelay());
+ }
+
+ private native Object nativeGetHillshadeIlluminationDirection();
+
+ private native TransitionOptions nativeGetHillshadeIlluminationDirectionTransition();
+
+ private native void nativeSetHillshadeIlluminationDirectionTransition(long duration, long delay);
+
+ private native Object nativeGetHillshadeIlluminationAnchor();
+
+ private native Object nativeGetHillshadeExaggeration();
+
+ private native TransitionOptions nativeGetHillshadeExaggerationTransition();
+
+ private native void nativeSetHillshadeExaggerationTransition(long duration, long delay);
+
+ private native Object nativeGetHillshadeShadowColor();
+
+ private native TransitionOptions nativeGetHillshadeShadowColorTransition();
+
+ private native void nativeSetHillshadeShadowColorTransition(long duration, long delay);
+
+ private native Object nativeGetHillshadeHighlightColor();
+
+ private native TransitionOptions nativeGetHillshadeHighlightColorTransition();
+
+ private native void nativeSetHillshadeHighlightColorTransition(long duration, long delay);
+
+ private native Object nativeGetHillshadeAccentColor();
+
+ private native TransitionOptions nativeGetHillshadeAccentColorTransition();
+
+ private native void nativeSetHillshadeAccentColorTransition(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/Property.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java
index 8d6c7dd055..e52474c35b 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/Property.java
@@ -570,6 +570,27 @@ public final class Property {
@Retention(RetentionPolicy.SOURCE)
public @interface FILL_EXTRUSION_TRANSLATE_ANCHOR {}
+ // HILLSHADE_ILLUMINATION_ANCHOR: Direction of light source when map is rotated.
+
+ /**
+ * The hillshade illumination is relative to the north direction.
+ */
+ public static final String HILLSHADE_ILLUMINATION_ANCHOR_MAP = "map";
+ /**
+ * The hillshade illumination is relative to the top of the viewport.
+ */
+ public static final String HILLSHADE_ILLUMINATION_ANCHOR_VIEWPORT = "viewport";
+
+ /**
+ * Direction of light source when map is rotated.
+ */
+ @StringDef({
+ HILLSHADE_ILLUMINATION_ANCHOR_MAP,
+ HILLSHADE_ILLUMINATION_ANCHOR_VIEWPORT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HILLSHADE_ILLUMINATION_ANCHOR {}
+
// ANCHOR: Whether extruded geometries are lit relative to the map or viewport.
/**
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 93d4cfa1b7..1b9106afaf 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
@@ -1988,6 +1988,234 @@ public class PropertyFactory {
}
/**
+ * The direction of the light source used to generate the hillshading with 0 as the top of the viewport if {@link Property.HILLSHADE_ILLUMINATION_ANCHOR} is set to `viewport` and due north if {@link Property.HILLSHADE_ILLUMINATION_ANCHOR} is set to `map`.
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue<Float> hillshadeIlluminationDirection(Float value) {
+ return new PaintPropertyValue<>("hillshade-illumination-direction", value);
+ }
+
+ /**
+ * The direction of the light source used to generate the hillshading with 0 as the top of the viewport if {@link Property.HILLSHADE_ILLUMINATION_ANCHOR} is set to `viewport` and due north if {@link Property.HILLSHADE_ILLUMINATION_ANCHOR} is set to `map`.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> hillshadeIlluminationDirection(Expression expression) {
+ return new PaintPropertyValue<>("hillshade-illumination-direction", expression);
+ }
+
+
+ /**
+ * The direction of the light source used to generate the hillshading with 0 as the top of the viewport if {@link Property.HILLSHADE_ILLUMINATION_ANCHOR} is set to `viewport` and due north if {@link Property.HILLSHADE_ILLUMINATION_ANCHOR} is set to `map`.
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> hillshadeIlluminationDirection(CameraFunction<Z, Float> function) {
+ return new PaintPropertyValue<>("hillshade-illumination-direction", function);
+ }
+
+ /**
+ * Direction of light source when map is rotated.
+ *
+ * @param value a String value
+ * @return property wrapper around String
+ */
+ public static PropertyValue<String> hillshadeIlluminationAnchor(@Property.HILLSHADE_ILLUMINATION_ANCHOR String value) {
+ return new PaintPropertyValue<>("hillshade-illumination-anchor", value);
+ }
+
+ /**
+ * Direction of light source when map is rotated.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> hillshadeIlluminationAnchor(Expression expression) {
+ return new PaintPropertyValue<>("hillshade-illumination-anchor", expression);
+ }
+
+
+ /**
+ * Direction of light source when map is rotated.
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for String
+ * @return property wrapper around a String function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> hillshadeIlluminationAnchor(CameraFunction<Z, String> function) {
+ return new PaintPropertyValue<>("hillshade-illumination-anchor", function);
+ }
+
+ /**
+ * Intensity of the hillshade
+ *
+ * @param value a Float value
+ * @return property wrapper around Float
+ */
+ public static PropertyValue<Float> hillshadeExaggeration(Float value) {
+ return new PaintPropertyValue<>("hillshade-exaggeration", value);
+ }
+
+ /**
+ * Intensity of the hillshade
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> hillshadeExaggeration(Expression expression) {
+ return new PaintPropertyValue<>("hillshade-exaggeration", expression);
+ }
+
+
+ /**
+ * Intensity of the hillshade
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for Float
+ * @return property wrapper around a Float function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, Float>> hillshadeExaggeration(CameraFunction<Z, Float> function) {
+ return new PaintPropertyValue<>("hillshade-exaggeration", function);
+ }
+
+ /**
+ * The shading color of areas that face away from the light source.
+ *
+ * @param value a int color value
+ * @return property wrapper around String color
+ */
+ public static PropertyValue<String> hillshadeShadowColor(@ColorInt int value) {
+ return new PaintPropertyValue<>("hillshade-shadow-color", colorToRgbaString(value));
+ }
+
+ /**
+ * The shading color of areas that face away from the light source.
+ *
+ * @param value a String value
+ * @return property wrapper around String
+ */
+ public static PropertyValue<String> hillshadeShadowColor(String value) {
+ return new PaintPropertyValue<>("hillshade-shadow-color", value);
+ }
+
+ /**
+ * The shading color of areas that face away from the light source.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> hillshadeShadowColor(Expression expression) {
+ return new PaintPropertyValue<>("hillshade-shadow-color", expression);
+ }
+
+
+ /**
+ * The shading color of areas that face away from the light source.
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for String
+ * @return property wrapper around a String function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> hillshadeShadowColor(CameraFunction<Z, String> function) {
+ return new PaintPropertyValue<>("hillshade-shadow-color", function);
+ }
+
+ /**
+ * The shading color of areas that faces towards the light source.
+ *
+ * @param value a int color value
+ * @return property wrapper around String color
+ */
+ public static PropertyValue<String> hillshadeHighlightColor(@ColorInt int value) {
+ return new PaintPropertyValue<>("hillshade-highlight-color", colorToRgbaString(value));
+ }
+
+ /**
+ * The shading color of areas that faces towards the light source.
+ *
+ * @param value a String value
+ * @return property wrapper around String
+ */
+ public static PropertyValue<String> hillshadeHighlightColor(String value) {
+ return new PaintPropertyValue<>("hillshade-highlight-color", value);
+ }
+
+ /**
+ * The shading color of areas that faces towards the light source.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> hillshadeHighlightColor(Expression expression) {
+ return new PaintPropertyValue<>("hillshade-highlight-color", expression);
+ }
+
+
+ /**
+ * The shading color of areas that faces towards the light source.
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for String
+ * @return property wrapper around a String function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> hillshadeHighlightColor(CameraFunction<Z, String> function) {
+ return new PaintPropertyValue<>("hillshade-highlight-color", function);
+ }
+
+ /**
+ * The shading color used to accentuate rugged terrain like sharp cliffs and gorges.
+ *
+ * @param value a int color value
+ * @return property wrapper around String color
+ */
+ public static PropertyValue<String> hillshadeAccentColor(@ColorInt int value) {
+ return new PaintPropertyValue<>("hillshade-accent-color", colorToRgbaString(value));
+ }
+
+ /**
+ * The shading color used to accentuate rugged terrain like sharp cliffs and gorges.
+ *
+ * @param value a String value
+ * @return property wrapper around String
+ */
+ public static PropertyValue<String> hillshadeAccentColor(String value) {
+ return new PaintPropertyValue<>("hillshade-accent-color", value);
+ }
+
+ /**
+ * The shading color used to accentuate rugged terrain like sharp cliffs and gorges.
+ *
+ * @param expression an expression statement
+ * @return property wrapper around an expression statement
+ */
+ public static PropertyValue<Expression> hillshadeAccentColor(Expression expression) {
+ return new PaintPropertyValue<>("hillshade-accent-color", expression);
+ }
+
+
+ /**
+ * The shading color used to accentuate rugged terrain like sharp cliffs and gorges.
+ *
+ * @param <Z> the zoom parameter type
+ * @param function a wrapper {@link CameraFunction} for String
+ * @return property wrapper around a String function
+ */
+ @Deprecated
+ public static <Z extends Number> PropertyValue<CameraFunction<Z, String>> hillshadeAccentColor(CameraFunction<Z, String> function) {
+ return new PaintPropertyValue<>("hillshade-accent-color", function);
+ }
+
+ /**
* The color with which the background will be drawn.
*
* @param value a int color value
diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
index 56e0af8b45..77fa11808e 100644
--- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
+++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/layers/layer.java.ejs
@@ -78,7 +78,7 @@ public class <%- camelize(type) %>Layer extends Layer {
}
<% } -%>
-<% if (type !== 'background' && type !== 'raster') { -%>
+<% if (type !== 'background' && type !== 'raster' && type !== 'hillshade') { -%>
/**
* Get the source layer.
*
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HillshadeLayerTest.java b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HillshadeLayerTest.java
new file mode 100644
index 0000000000..c76dccdedb
--- /dev/null
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/HillshadeLayerTest.java
@@ -0,0 +1,523 @@
+// 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.HillshadeLayer;
+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 HillshadeLayer
+ */
+@RunWith(AndroidJUnit4.class)
+public class HillshadeLayerTest extends BaseActivityTest {
+
+ private HillshadeLayer 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 HillshadeLayer("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 testHillshadeIlluminationDirectionTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-illumination-directionTransitionOptions");
+ 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.setHillshadeIlluminationDirectionTransition(options);
+ assertEquals(layer.getHillshadeIlluminationDirectionTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeIlluminationDirectionAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-illumination-direction");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeIlluminationDirection(0.3f));
+ assertEquals((Float) layer.getHillshadeIlluminationDirection().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeIlluminationDirectionAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-illumination-direction");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ hillshadeIlluminationDirection(
+ zoom(
+ exponential(
+ stop(2, hillshadeIlluminationDirection(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHillshadeIlluminationDirection());
+ assertNotNull(layer.getHillshadeIlluminationDirection().getFunction());
+ assertEquals(CameraFunction.class, layer.getHillshadeIlluminationDirection().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHillshadeIlluminationDirection().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHillshadeIlluminationDirection().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHillshadeIlluminationDirection().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeIlluminationAnchorAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-illumination-anchor");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeIlluminationAnchor(HILLSHADE_ILLUMINATION_ANCHOR_MAP));
+ assertEquals((String) layer.getHillshadeIlluminationAnchor().getValue(), (String) HILLSHADE_ILLUMINATION_ANCHOR_MAP);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeIlluminationAnchorAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-illumination-anchor");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ hillshadeIlluminationAnchor(
+ zoom(
+ interval(
+ stop(2, hillshadeIlluminationAnchor(HILLSHADE_ILLUMINATION_ANCHOR_MAP))
+ )
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHillshadeIlluminationAnchor());
+ assertNotNull(layer.getHillshadeIlluminationAnchor().getFunction());
+ assertEquals(CameraFunction.class, layer.getHillshadeIlluminationAnchor().getFunction().getClass());
+ assertEquals(IntervalStops.class, layer.getHillshadeIlluminationAnchor().getFunction().getStops().getClass());
+ assertEquals(1, ((IntervalStops) layer.getHillshadeIlluminationAnchor().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeExaggerationTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-exaggerationTransitionOptions");
+ 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.setHillshadeExaggerationTransition(options);
+ assertEquals(layer.getHillshadeExaggerationTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeExaggerationAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-exaggeration");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeExaggeration(0.3f));
+ assertEquals((Float) layer.getHillshadeExaggeration().getValue(), (Float) 0.3f);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeExaggerationAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-exaggeration");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ hillshadeExaggeration(
+ zoom(
+ exponential(
+ stop(2, hillshadeExaggeration(0.3f))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHillshadeExaggeration());
+ assertNotNull(layer.getHillshadeExaggeration().getFunction());
+ assertEquals(CameraFunction.class, layer.getHillshadeExaggeration().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHillshadeExaggeration().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHillshadeExaggeration().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHillshadeExaggeration().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeShadowColorTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-shadow-colorTransitionOptions");
+ 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.setHillshadeShadowColorTransition(options);
+ assertEquals(layer.getHillshadeShadowColorTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeShadowColorAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-shadow-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeShadowColor("rgba(0, 0, 0, 1)"));
+ assertEquals((String) layer.getHillshadeShadowColor().getValue(), (String) "rgba(0, 0, 0, 1)");
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeShadowColorAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-shadow-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ hillshadeShadowColor(
+ zoom(
+ exponential(
+ stop(2, hillshadeShadowColor("rgba(0, 0, 0, 1)"))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHillshadeShadowColor());
+ assertNotNull(layer.getHillshadeShadowColor().getFunction());
+ assertEquals(CameraFunction.class, layer.getHillshadeShadowColor().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHillshadeShadowColor().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHillshadeShadowColor().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHillshadeShadowColor().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeShadowColorAsIntConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-shadow-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeShadowColor(Color.RED));
+ assertEquals(layer.getHillshadeShadowColorAsInt(), Color.RED);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeHighlightColorTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-highlight-colorTransitionOptions");
+ 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.setHillshadeHighlightColorTransition(options);
+ assertEquals(layer.getHillshadeHighlightColorTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeHighlightColorAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-highlight-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeHighlightColor("rgba(0, 0, 0, 1)"));
+ assertEquals((String) layer.getHillshadeHighlightColor().getValue(), (String) "rgba(0, 0, 0, 1)");
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeHighlightColorAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-highlight-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ hillshadeHighlightColor(
+ zoom(
+ exponential(
+ stop(2, hillshadeHighlightColor("rgba(0, 0, 0, 1)"))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHillshadeHighlightColor());
+ assertNotNull(layer.getHillshadeHighlightColor().getFunction());
+ assertEquals(CameraFunction.class, layer.getHillshadeHighlightColor().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHillshadeHighlightColor().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHillshadeHighlightColor().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHillshadeHighlightColor().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeHighlightColorAsIntConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-highlight-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeHighlightColor(Color.RED));
+ assertEquals(layer.getHillshadeHighlightColorAsInt(), Color.RED);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeAccentColorTransition() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-accent-colorTransitionOptions");
+ 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.setHillshadeAccentColorTransition(options);
+ assertEquals(layer.getHillshadeAccentColorTransition(), options);
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeAccentColorAsConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-accent-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeAccentColor("rgba(0, 0, 0, 1)"));
+ assertEquals((String) layer.getHillshadeAccentColor().getValue(), (String) "rgba(0, 0, 0, 1)");
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeAccentColorAsCameraFunction() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-accent-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set
+ layer.setProperties(
+ hillshadeAccentColor(
+ zoom(
+ exponential(
+ stop(2, hillshadeAccentColor("rgba(0, 0, 0, 1)"))
+ ).withBase(0.5f)
+ )
+ )
+ );
+
+ // Verify
+ assertNotNull(layer.getHillshadeAccentColor());
+ assertNotNull(layer.getHillshadeAccentColor().getFunction());
+ assertEquals(CameraFunction.class, layer.getHillshadeAccentColor().getFunction().getClass());
+ assertEquals(ExponentialStops.class, layer.getHillshadeAccentColor().getFunction().getStops().getClass());
+ assertEquals(0.5f, ((ExponentialStops) layer.getHillshadeAccentColor().getFunction().getStops()).getBase(), 0.001);
+ assertEquals(1, ((ExponentialStops) layer.getHillshadeAccentColor().getFunction().getStops()).size());
+ }
+ });
+ }
+
+ @Test
+ public void testHillshadeAccentColorAsIntConstant() {
+ validateTestSetup();
+ setupLayer();
+ Timber.i("hillshade-accent-color");
+ invoke(mapboxMap, new MapboxMapAction.OnInvokeActionListener() {
+ @Override
+ public void onInvokeAction(UiController uiController, MapboxMap mapboxMap) {
+ assertNotNull(layer);
+
+ // Set and Get
+ layer.setProperties(hillshadeAccentColor(Color.RED));
+ assertEquals(layer.getHillshadeAccentColorAsInt(), Color.RED);
+ }
+ });
+ }
+
+}
diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs
index 192740f708..206497b860 100644
--- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs
+++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/style/layer.junit.ejs
@@ -99,7 +99,7 @@ public class <%- camelize(type) %>LayerTest extends BaseActivityTest {
}
});
}
-<% if (!(type === 'background' || type === 'raster')) { -%>
+<% if (!(type === 'background' || type === 'raster' || type === 'hillshade')) { -%>
@Test
public void testSourceLayer() {
diff --git a/platform/android/config.cmake b/platform/android/config.cmake
index f5de7a6052..54c6bbee40 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/hillshade_layer.cpp
+ platform/android/src/style/layers/hillshade_layer.hpp
platform/android/src/style/layers/layer.cpp
platform/android/src/style/layers/layer.hpp
platform/android/src/style/layers/layers.cpp
diff --git a/platform/android/src/style/conversion/types.hpp b/platform/android/src/style/conversion/types.hpp
index 375d1a33aa..8a75b870b3 100644
--- a/platform/android/src/style/conversion/types.hpp
+++ b/platform/android/src/style/conversion/types.hpp
@@ -93,6 +93,13 @@ struct Converter<jni::jobject*, mbgl::style::CirclePitchScaleType> {
};
template <>
+struct Converter<jni::jobject*, mbgl::style::HillshadeIlluminationAnchorType> {
+ Result<jni::jobject*> operator()(jni::JNIEnv& env, const mbgl::style::HillshadeIlluminationAnchorType& value) const {
+ return convert<jni::jobject*, std::string>(env, toString(value));
+ }
+};
+
+template <>
struct Converter<jni::jobject*, mbgl::style::LightAnchorType> {
Result<jni::jobject*> operator()(jni::JNIEnv& env, const mbgl::style::LightAnchorType& value) const {
return convert<jni::jobject*, std::string>(env, toString(value));
diff --git a/platform/android/src/style/conversion/types_string_values.hpp b/platform/android/src/style/conversion/types_string_values.hpp
index a19ca33a2f..7e4fd4a7f7 100644
--- a/platform/android/src/style/conversion/types_string_values.hpp
+++ b/platform/android/src/style/conversion/types_string_values.hpp
@@ -206,6 +206,20 @@ namespace conversion {
}
}
+ // hillshade-illumination-anchor
+ inline std::string toString(mbgl::style::HillshadeIlluminationAnchorType value) {
+ switch (value) {
+ case mbgl::style::HillshadeIlluminationAnchorType::Map:
+ return "map";
+ break;
+ case mbgl::style::HillshadeIlluminationAnchorType::Viewport:
+ return "viewport";
+ break;
+ default:
+ throw std::runtime_error("Not implemented");
+ }
+ }
+
// anchor
inline std::string toString(mbgl::style::LightAnchorType value) {
switch (value) {
diff --git a/platform/android/src/style/layers/hillshade_layer.cpp b/platform/android/src/style/layers/hillshade_layer.cpp
new file mode 100644
index 0000000000..b58bc3b947
--- /dev/null
+++ b/platform/android/src/style/layers/hillshade_layer.cpp
@@ -0,0 +1,178 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#include "hillshade_layer.hpp"
+
+#include <string>
+
+#include "../conversion/property_value.hpp"
+#include "../conversion/transition_options.hpp"
+
+namespace mbgl {
+namespace android {
+
+ /**
+ * Creates an owning peer object (for layers not attached to the map) from the JVM side
+ */
+ HillshadeLayer::HillshadeLayer(jni::JNIEnv& env, jni::String layerId, jni::String sourceId)
+ : Layer(env, std::make_unique<mbgl::style::HillshadeLayer>(jni::Make<std::string>(env, layerId), jni::Make<std::string>(env, sourceId))) {
+ }
+
+ /**
+ * Creates a non-owning peer object (for layers currently attached to the map)
+ */
+ HillshadeLayer::HillshadeLayer(mbgl::Map& map, mbgl::style::HillshadeLayer& coreLayer)
+ : Layer(map, coreLayer) {
+ }
+
+ /**
+ * Creates an owning peer object (for layers not attached to the map)
+ */
+ HillshadeLayer::HillshadeLayer(mbgl::Map& map, std::unique_ptr<mbgl::style::HillshadeLayer> coreLayer)
+ : Layer(map, std::move(coreLayer)) {
+ }
+
+ HillshadeLayer::~HillshadeLayer() = default;
+
+ // Property getters
+
+ jni::Object<jni::ObjectTag> HillshadeLayer::getHillshadeIlluminationDirection(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeIlluminationDirection());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HillshadeLayer::getHillshadeIlluminationDirectionTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeIlluminationDirectionTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HillshadeLayer::setHillshadeIlluminationDirectionTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::setHillshadeIlluminationDirectionTransition(options);
+ }
+
+ jni::Object<jni::ObjectTag> HillshadeLayer::getHillshadeIlluminationAnchor(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeIlluminationAnchor());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<jni::ObjectTag> HillshadeLayer::getHillshadeExaggeration(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeExaggeration());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HillshadeLayer::getHillshadeExaggerationTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeExaggerationTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HillshadeLayer::setHillshadeExaggerationTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::setHillshadeExaggerationTransition(options);
+ }
+
+ jni::Object<jni::ObjectTag> HillshadeLayer::getHillshadeShadowColor(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeShadowColor());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HillshadeLayer::getHillshadeShadowColorTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeShadowColorTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HillshadeLayer::setHillshadeShadowColorTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::setHillshadeShadowColorTransition(options);
+ }
+
+ jni::Object<jni::ObjectTag> HillshadeLayer::getHillshadeHighlightColor(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeHighlightColor());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HillshadeLayer::getHillshadeHighlightColorTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeHighlightColorTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HillshadeLayer::setHillshadeHighlightColorTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::setHillshadeHighlightColorTransition(options);
+ }
+
+ jni::Object<jni::ObjectTag> HillshadeLayer::getHillshadeAccentColor(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ Result<jni::jobject*> converted = convert<jni::jobject*>(env, layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeAccentColor());
+ return jni::Object<jni::ObjectTag>(*converted);
+ }
+
+ jni::Object<TransitionOptions> HillshadeLayer::getHillshadeAccentColorTransition(jni::JNIEnv& env) {
+ using namespace mbgl::android::conversion;
+ mbgl::style::TransitionOptions options = layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::getHillshadeAccentColorTransition();
+ return *convert<jni::Object<TransitionOptions>>(env, options);
+ }
+
+ void HillshadeLayer::setHillshadeAccentColorTransition(jni::JNIEnv&, jlong duration, jlong delay) {
+ mbgl::style::TransitionOptions options;
+ options.duration.emplace(mbgl::Milliseconds(duration));
+ options.delay.emplace(mbgl::Milliseconds(delay));
+ layer.as<mbgl::style::HillshadeLayer>()->HillshadeLayer::setHillshadeAccentColorTransition(options);
+ }
+
+
+ jni::Class<HillshadeLayer> HillshadeLayer::javaClass;
+
+ jni::jobject* HillshadeLayer::createJavaPeer(jni::JNIEnv& env) {
+ static auto constructor = HillshadeLayer::javaClass.template GetConstructor<jni::jlong>(env);
+ return HillshadeLayer::javaClass.New(env, constructor, reinterpret_cast<jni::jlong>(this));
+ }
+
+ void HillshadeLayer::registerNative(jni::JNIEnv& env) {
+ // Lookup the class
+ HillshadeLayer::javaClass = *jni::Class<HillshadeLayer>::Find(env).NewGlobalRef(env).release();
+
+ #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
+
+ // Register the peer
+ jni::RegisterNativePeer<HillshadeLayer>(
+ env, HillshadeLayer::javaClass, "nativePtr",
+ std::make_unique<HillshadeLayer, JNIEnv&, jni::String, jni::String>,
+ "initialize",
+ "finalize",
+ METHOD(&HillshadeLayer::getHillshadeIlluminationDirectionTransition, "nativeGetHillshadeIlluminationDirectionTransition"),
+ METHOD(&HillshadeLayer::setHillshadeIlluminationDirectionTransition, "nativeSetHillshadeIlluminationDirectionTransition"),
+ METHOD(&HillshadeLayer::getHillshadeIlluminationDirection, "nativeGetHillshadeIlluminationDirection"),
+ METHOD(&HillshadeLayer::getHillshadeIlluminationAnchor, "nativeGetHillshadeIlluminationAnchor"),
+ METHOD(&HillshadeLayer::getHillshadeExaggerationTransition, "nativeGetHillshadeExaggerationTransition"),
+ METHOD(&HillshadeLayer::setHillshadeExaggerationTransition, "nativeSetHillshadeExaggerationTransition"),
+ METHOD(&HillshadeLayer::getHillshadeExaggeration, "nativeGetHillshadeExaggeration"),
+ METHOD(&HillshadeLayer::getHillshadeShadowColorTransition, "nativeGetHillshadeShadowColorTransition"),
+ METHOD(&HillshadeLayer::setHillshadeShadowColorTransition, "nativeSetHillshadeShadowColorTransition"),
+ METHOD(&HillshadeLayer::getHillshadeShadowColor, "nativeGetHillshadeShadowColor"),
+ METHOD(&HillshadeLayer::getHillshadeHighlightColorTransition, "nativeGetHillshadeHighlightColorTransition"),
+ METHOD(&HillshadeLayer::setHillshadeHighlightColorTransition, "nativeSetHillshadeHighlightColorTransition"),
+ METHOD(&HillshadeLayer::getHillshadeHighlightColor, "nativeGetHillshadeHighlightColor"),
+ METHOD(&HillshadeLayer::getHillshadeAccentColorTransition, "nativeGetHillshadeAccentColorTransition"),
+ METHOD(&HillshadeLayer::setHillshadeAccentColorTransition, "nativeSetHillshadeAccentColorTransition"),
+ METHOD(&HillshadeLayer::getHillshadeAccentColor, "nativeGetHillshadeAccentColor"));
+ }
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/style/layers/hillshade_layer.hpp b/platform/android/src/style/layers/hillshade_layer.hpp
new file mode 100644
index 0000000000..101febb228
--- /dev/null
+++ b/platform/android/src/style/layers/hillshade_layer.hpp
@@ -0,0 +1,58 @@
+// This file is generated. Edit android/platform/scripts/generate-style-code.js, then run `make android-style-code`.
+
+#pragma once
+
+#include "layer.hpp"
+#include "../transition_options.hpp"
+#include <mbgl/style/layers/hillshade_layer.hpp>
+#include <jni/jni.hpp>
+
+namespace mbgl {
+namespace android {
+
+class HillshadeLayer : public Layer {
+public:
+
+ static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/layers/HillshadeLayer"; };
+
+ static jni::Class<HillshadeLayer> javaClass;
+
+ static void registerNative(jni::JNIEnv&);
+
+ HillshadeLayer(jni::JNIEnv&, jni::String, jni::String);
+
+ HillshadeLayer(mbgl::Map&, mbgl::style::HillshadeLayer&);
+
+ HillshadeLayer(mbgl::Map&, std::unique_ptr<mbgl::style::HillshadeLayer>);
+
+ ~HillshadeLayer();
+
+ // Properties
+
+ jni::Object<jni::ObjectTag> getHillshadeIlluminationDirection(jni::JNIEnv&);
+ void setHillshadeIlluminationDirectionTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHillshadeIlluminationDirectionTransition(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHillshadeIlluminationAnchor(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHillshadeExaggeration(jni::JNIEnv&);
+ void setHillshadeExaggerationTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHillshadeExaggerationTransition(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHillshadeShadowColor(jni::JNIEnv&);
+ void setHillshadeShadowColorTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHillshadeShadowColorTransition(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHillshadeHighlightColor(jni::JNIEnv&);
+ void setHillshadeHighlightColorTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHillshadeHighlightColorTransition(jni::JNIEnv&);
+
+ jni::Object<jni::ObjectTag> getHillshadeAccentColor(jni::JNIEnv&);
+ void setHillshadeAccentColorTransition(jni::JNIEnv&, jlong duration, jlong delay);
+ jni::Object<TransitionOptions> getHillshadeAccentColorTransition(jni::JNIEnv&);
+ jni::jobject* createJavaPeer(jni::JNIEnv&);
+
+}; // class HillshadeLayer
+
+} // namespace android
+} // namespace mbgl
diff --git a/platform/android/src/style/layers/layer.cpp b/platform/android/src/style/layers/layer.cpp
index 31032b117f..da1550bdb1 100644
--- a/platform/android/src/style/layers/layer.cpp
+++ b/platform/android/src/style/layers/layer.cpp
@@ -10,6 +10,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
@@ -110,6 +111,7 @@ namespace android {
void operator()(style::BackgroundLayer&) { Log::Warning(mbgl::Event::JNI, "BackgroundLayer doesn't support filters"); }
void operator()(style::CustomLayer&) { Log::Warning(mbgl::Event::JNI, "CustomLayer doesn't support filters"); }
void operator()(style::RasterLayer&) { Log::Warning(mbgl::Event::JNI, "RasterLayer doesn't support filters"); }
+ void operator()(style::HillshadeLayer&) { Log::Warning(mbgl::Event::JNI, "HillshadeLayer doesn't support filters"); }
template <class LayerType>
void operator()(LayerType& layer) {
@@ -137,6 +139,7 @@ namespace android {
void operator()(style::BackgroundLayer&) { Log::Warning(mbgl::Event::JNI, "BackgroundLayer doesn't support source layer"); }
void operator()(style::CustomLayer&) { Log::Warning(mbgl::Event::JNI, "CustomLayer doesn't support source layer"); }
void operator()(style::RasterLayer&) { Log::Warning(mbgl::Event::JNI, "RasterLayer doesn't support source layer"); }
+ void operator()(style::HillshadeLayer&) { Log::Warning(mbgl::Event::JNI, "HillshadeLayer doesn't support source layer"); }
template <class LayerType>
void operator()(LayerType& layer) {
@@ -157,6 +160,7 @@ namespace android {
std::string operator()(style::BackgroundLayer&) { return noop("BackgroundLayer"); }
std::string operator()(style::CustomLayer&) { return noop("CustomLayer"); }
std::string operator()(style::RasterLayer&) { return noop("RasterLayer"); }
+ std::string operator()(style::HillshadeLayer&) { return noop("HillshadeLayer"); }
template <class LayerType>
std::string operator()(LayerType& layer) {
diff --git a/platform/android/src/style/layers/layers.cpp b/platform/android/src/style/layers/layers.cpp
index 9803b6f841..5d1d1bbcbf 100644
--- a/platform/android/src/style/layers/layers.cpp
+++ b/platform/android/src/style/layers/layers.cpp
@@ -5,6 +5,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
@@ -15,6 +16,7 @@
#include "custom_layer.hpp"
#include "fill_extrusion_layer.hpp"
#include "fill_layer.hpp"
+#include "hillshade_layer.hpp"
#include "line_layer.hpp"
#include "raster_layer.hpp"
#include "symbol_layer.hpp"
@@ -30,6 +32,7 @@ template <> struct PeerType<style::BackgroundLayer> { using Type = android::Back
template <> struct PeerType<style::CircleLayer> { using Type = android::CircleLayer; };
template <> struct PeerType<style::FillExtrusionLayer> { using Type = android::FillExtrusionLayer; };
template <> struct PeerType<style::FillLayer> { using Type = android::FillLayer; };
+template <> struct PeerType<style::HillshadeLayer> { using Type = android::HillshadeLayer; };
template <> struct PeerType<style::LineLayer> { using Type = android::LineLayer; };
template <> struct PeerType<style::RasterLayer> { using Type = android::RasterLayer; };
template <> struct PeerType<style::SymbolLayer> { using Type = android::SymbolLayer; };
@@ -92,6 +95,7 @@ void registerNativeLayers(jni::JNIEnv& env) {
CustomLayer::registerNative(env);
FillExtrusionLayer::registerNative(env);
FillLayer::registerNative(env);
+ HillshadeLayer::registerNative(env);
LineLayer::registerNative(env);
RasterLayer::registerNative(env);
SymbolLayer::registerNative(env);
diff --git a/platform/darwin/src/MGLHillshadeStyleLayer.h b/platform/darwin/src/MGLHillshadeStyleLayer.h
new file mode 100644
index 0000000000..0fe49d510d
--- /dev/null
+++ b/platform/darwin/src/MGLHillshadeStyleLayer.h
@@ -0,0 +1,276 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLFoundation.h"
+#import "MGLStyleValue.h"
+#import "MGLVectorStyleLayer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ Direction of light source when map is rotated.
+
+ Values of this type are used in the `MGLHillshadeStyleLayer.hillshadeIlluminationAnchor`
+ property.
+ */
+typedef NS_ENUM(NSUInteger, MGLHillshadeIlluminationAnchor) {
+ /**
+ The hillshade illumination is relative to the north direction.
+ */
+ MGLHillshadeIlluminationAnchorMap,
+ /**
+ The hillshade illumination is relative to the top of the viewport.
+ */
+ MGLHillshadeIlluminationAnchorViewport,
+};
+
+/**
+ Client-side hillshading visualization based on DEM data. Currently, the
+ implementation only supports Mapbox Terrain RGB tiles
+
+ You can access an existing hillshade 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 hillshade style layer and add it to the style using a method such as
+ `-[MGLStyle addLayer:]`.
+
+ ### Example
+
+ ```swift
+ ```
+ */
+MGL_EXPORT
+@interface MGLHillshadeStyleLayer : MGLVectorStyleLayer
+
+/**
+ Returns a hillshade 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
+
+#if TARGET_OS_IPHONE
+/**
+ The shading color used to accentuate rugged terrain like sharp cliffs and
+ gorges.
+
+ The default value of this property is an `MGLStyleValue` object containing
+ `UIColor.blackColor`. Set this property to `nil` to reset it to the default
+ value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<UIColor *> *hillshadeAccentColor;
+#else
+/**
+ The shading color used to accentuate rugged terrain like sharp cliffs and
+ gorges.
+
+ The default value of this property is an `MGLStyleValue` object containing
+ `NSColor.blackColor`. Set this property to `nil` to reset it to the default
+ value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<NSColor *> *hillshadeAccentColor;
+#endif
+
+/**
+ The transition affecting any changes to this layer’s `hillshadeAccentColor` property.
+
+ This property corresponds to the `hillshade-accent-color-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition hillshadeAccentColorTransition;
+
+/**
+ Intensity of the hillshade
+
+ The default value of this property is an `MGLStyleValue` object containing an
+ `NSNumber` object containing the float `0.5`. Set this property to `nil` to
+ reset it to the default value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<NSNumber *> *hillshadeExaggeration;
+
+/**
+ The transition affecting any changes to this layer’s `hillshadeExaggeration` property.
+
+ This property corresponds to the `hillshade-exaggeration-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition hillshadeExaggerationTransition;
+
+#if TARGET_OS_IPHONE
+/**
+ The shading color of areas that faces towards the light source.
+
+ The default value of this property is an `MGLStyleValue` object containing
+ `UIColor.whiteColor`. Set this property to `nil` to reset it to the default
+ value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<UIColor *> *hillshadeHighlightColor;
+#else
+/**
+ The shading color of areas that faces towards the light source.
+
+ The default value of this property is an `MGLStyleValue` object containing
+ `NSColor.whiteColor`. Set this property to `nil` to reset it to the default
+ value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<NSColor *> *hillshadeHighlightColor;
+#endif
+
+/**
+ The transition affecting any changes to this layer’s `hillshadeHighlightColor` property.
+
+ This property corresponds to the `hillshade-highlight-color-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition hillshadeHighlightColorTransition;
+
+/**
+ Direction of light source when map is rotated.
+
+ The default value of this property is an `MGLStyleValue` object containing an
+ `NSValue` object containing `MGLHillshadeIlluminationAnchorViewport`. Set this
+ property to `nil` to reset it to the default value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of
+ `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<NSValue *> *hillshadeIlluminationAnchor;
+
+/**
+ The direction of the light source used to generate the hillshading with 0 as
+ the top of the viewport if `hillshadeIlluminationAnchor` is set to
+ `MGLHillshadeIlluminationAnchorViewport` and due north if
+ `hillshadeIlluminationAnchor` is set to `MGLHillshadeIlluminationAnchorMap`.
+
+ The default value of this property is an `MGLStyleValue` object containing an
+ `NSNumber` object containing the float `335`. Set this property to `nil` to
+ reset it to the default value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<NSNumber *> *hillshadeIlluminationDirection;
+
+/**
+ The transition affecting any changes to this layer’s `hillshadeIlluminationDirection` property.
+
+ This property corresponds to the `hillshade-illumination-direction-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition hillshadeIlluminationDirectionTransition;
+
+#if TARGET_OS_IPHONE
+/**
+ The shading color of areas that face away from the light source.
+
+ The default value of this property is an `MGLStyleValue` object containing
+ `UIColor.blackColor`. Set this property to `nil` to reset it to the default
+ value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<UIColor *> *hillshadeShadowColor;
+#else
+/**
+ The shading color of areas that face away from the light source.
+
+ The default value of this property is an `MGLStyleValue` object containing
+ `NSColor.blackColor`. Set this property to `nil` to reset it to the default
+ value.
+
+ You can set this property to an instance of:
+
+ * `MGLConstantStyleValue`
+ * `MGLCameraStyleFunction` with an interpolation mode of:
+ * `MGLInterpolationModeExponential`
+ * `MGLInterpolationModeInterval`
+ */
+@property (nonatomic, null_resettable) MGLStyleValue<NSColor *> *hillshadeShadowColor;
+#endif
+
+/**
+ The transition affecting any changes to this layer’s `hillshadeShadowColor` property.
+
+ This property corresponds to the `hillshade-shadow-color-transition` property in the style JSON file format.
+*/
+@property (nonatomic) MGLTransition hillshadeShadowColorTransition;
+
+@end
+
+/**
+ Methods for wrapping an enumeration value for a style layer attribute in an
+ `MGLHillshadeStyleLayer` object and unwrapping its raw value.
+ */
+@interface NSValue (MGLHillshadeStyleLayerAdditions)
+
+#pragma mark Working with Hillshade Style Layer Attribute Values
+
+/**
+ Creates a new value object containing the given `MGLHillshadeIlluminationAnchor` enumeration.
+
+ @param hillshadeIlluminationAnchor The value for the new object.
+ @return A new value object that contains the enumeration value.
+ */
++ (instancetype)valueWithMGLHillshadeIlluminationAnchor:(MGLHillshadeIlluminationAnchor)hillshadeIlluminationAnchor;
+
+/**
+ The `MGLHillshadeIlluminationAnchor` enumeration representation of the value.
+ */
+@property (readonly) MGLHillshadeIlluminationAnchor MGLHillshadeIlluminationAnchorValue;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/platform/darwin/src/MGLHillshadeStyleLayer.mm b/platform/darwin/src/MGLHillshadeStyleLayer.mm
new file mode 100644
index 0000000000..3225feb587
--- /dev/null
+++ b/platform/darwin/src/MGLHillshadeStyleLayer.mm
@@ -0,0 +1,286 @@
+// 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 "MGLHillshadeStyleLayer.h"
+
+#include <mbgl/style/transition_options.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
+
+namespace mbgl {
+
+ MBGL_DEFINE_ENUM(MGLHillshadeIlluminationAnchor, {
+ { MGLHillshadeIlluminationAnchorMap, "map" },
+ { MGLHillshadeIlluminationAnchorViewport, "viewport" },
+ });
+
+}
+
+@interface MGLHillshadeStyleLayer ()
+
+@property (nonatomic, readonly) mbgl::style::HillshadeLayer *rawLayer;
+
+@end
+
+@implementation MGLHillshadeStyleLayer
+
+- (instancetype)initWithIdentifier:(NSString *)identifier source:(MGLSource *)source
+{
+ auto layer = std::make_unique<mbgl::style::HillshadeLayer>(identifier.UTF8String, source.identifier.UTF8String);
+ return self = [super initWithPendingLayer:std::move(layer)];
+}
+
+- (mbgl::style::HillshadeLayer *)rawLayer
+{
+ return (mbgl::style::HillshadeLayer *)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)setHillshadeAccentColor:(MGLStyleValue<MGLColor *> *)hillshadeAccentColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toInterpolatablePropertyValue(hillshadeAccentColor);
+ self.rawLayer->setHillshadeAccentColor(mbglValue);
+}
+
+- (MGLStyleValue<MGLColor *> *)hillshadeAccentColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHillshadeAccentColor();
+ if (propertyValue.isUndefined()) {
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toStyleValue(self.rawLayer->getDefaultHillshadeAccentColor());
+ }
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toStyleValue(propertyValue);
+}
+
+- (void)setHillshadeAccentColorTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHillshadeAccentColorTransition(options);
+}
+
+- (MGLTransition)hillshadeAccentColorTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHillshadeAccentColorTransition();
+ 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)setHillshadeExaggeration:(MGLStyleValue<NSNumber *> *)hillshadeExaggeration {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toInterpolatablePropertyValue(hillshadeExaggeration);
+ self.rawLayer->setHillshadeExaggeration(mbglValue);
+}
+
+- (MGLStyleValue<NSNumber *> *)hillshadeExaggeration {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHillshadeExaggeration();
+ if (propertyValue.isUndefined()) {
+ return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(self.rawLayer->getDefaultHillshadeExaggeration());
+ }
+ return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(propertyValue);
+}
+
+- (void)setHillshadeExaggerationTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHillshadeExaggerationTransition(options);
+}
+
+- (MGLTransition)hillshadeExaggerationTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHillshadeExaggerationTransition();
+ 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)setHillshadeHighlightColor:(MGLStyleValue<MGLColor *> *)hillshadeHighlightColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toInterpolatablePropertyValue(hillshadeHighlightColor);
+ self.rawLayer->setHillshadeHighlightColor(mbglValue);
+}
+
+- (MGLStyleValue<MGLColor *> *)hillshadeHighlightColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHillshadeHighlightColor();
+ if (propertyValue.isUndefined()) {
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toStyleValue(self.rawLayer->getDefaultHillshadeHighlightColor());
+ }
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toStyleValue(propertyValue);
+}
+
+- (void)setHillshadeHighlightColorTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHillshadeHighlightColorTransition(options);
+}
+
+- (MGLTransition)hillshadeHighlightColorTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHillshadeHighlightColorTransition();
+ 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)setHillshadeIlluminationAnchor:(MGLStyleValue<NSValue *> *)hillshadeIlluminationAnchor {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<mbgl::style::HillshadeIlluminationAnchorType, NSValue *, mbgl::style::HillshadeIlluminationAnchorType, MGLHillshadeIlluminationAnchor>().toEnumPropertyValue(hillshadeIlluminationAnchor);
+ self.rawLayer->setHillshadeIlluminationAnchor(mbglValue);
+}
+
+- (MGLStyleValue<NSValue *> *)hillshadeIlluminationAnchor {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHillshadeIlluminationAnchor();
+ if (propertyValue.isUndefined()) {
+ return MGLStyleValueTransformer<mbgl::style::HillshadeIlluminationAnchorType, NSValue *, mbgl::style::HillshadeIlluminationAnchorType, MGLHillshadeIlluminationAnchor>().toEnumStyleValue(self.rawLayer->getDefaultHillshadeIlluminationAnchor());
+ }
+ return MGLStyleValueTransformer<mbgl::style::HillshadeIlluminationAnchorType, NSValue *, mbgl::style::HillshadeIlluminationAnchorType, MGLHillshadeIlluminationAnchor>().toEnumStyleValue(propertyValue);
+}
+
+- (void)setHillshadeIlluminationDirection:(MGLStyleValue<NSNumber *> *)hillshadeIlluminationDirection {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<float, NSNumber *>().toInterpolatablePropertyValue(hillshadeIlluminationDirection);
+ self.rawLayer->setHillshadeIlluminationDirection(mbglValue);
+}
+
+- (MGLStyleValue<NSNumber *> *)hillshadeIlluminationDirection {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHillshadeIlluminationDirection();
+ if (propertyValue.isUndefined()) {
+ return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(self.rawLayer->getDefaultHillshadeIlluminationDirection());
+ }
+ return MGLStyleValueTransformer<float, NSNumber *>().toStyleValue(propertyValue);
+}
+
+- (void)setHillshadeIlluminationDirectionTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHillshadeIlluminationDirectionTransition(options);
+}
+
+- (MGLTransition)hillshadeIlluminationDirectionTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHillshadeIlluminationDirectionTransition();
+ 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)setHillshadeShadowColor:(MGLStyleValue<MGLColor *> *)hillshadeShadowColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto mbglValue = MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toInterpolatablePropertyValue(hillshadeShadowColor);
+ self.rawLayer->setHillshadeShadowColor(mbglValue);
+}
+
+- (MGLStyleValue<MGLColor *> *)hillshadeShadowColor {
+ MGLAssertStyleLayerIsValid();
+
+ auto propertyValue = self.rawLayer->getHillshadeShadowColor();
+ if (propertyValue.isUndefined()) {
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toStyleValue(self.rawLayer->getDefaultHillshadeShadowColor());
+ }
+ return MGLStyleValueTransformer<mbgl::Color, MGLColor *>().toStyleValue(propertyValue);
+}
+
+- (void)setHillshadeShadowColorTransition:(MGLTransition )transition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions options { { MGLDurationFromTimeInterval(transition.duration) }, { MGLDurationFromTimeInterval(transition.delay) } };
+ self.rawLayer->setHillshadeShadowColorTransition(options);
+}
+
+- (MGLTransition)hillshadeShadowColorTransition {
+ MGLAssertStyleLayerIsValid();
+
+ mbgl::style::TransitionOptions transitionOptions = self.rawLayer->getHillshadeShadowColorTransition();
+ MGLTransition transition;
+ transition.duration = MGLTimeIntervalFromDuration(transitionOptions.duration.value_or(mbgl::Duration::zero()));
+ transition.delay = MGLTimeIntervalFromDuration(transitionOptions.delay.value_or(mbgl::Duration::zero()));
+
+ return transition;
+}
+
+@end
+
+@implementation NSValue (MGLHillshadeStyleLayerAdditions)
+
++ (NSValue *)valueWithMGLHillshadeIlluminationAnchor:(MGLHillshadeIlluminationAnchor)hillshadeIlluminationAnchor {
+ return [NSValue value:&hillshadeIlluminationAnchor withObjCType:@encode(MGLHillshadeIlluminationAnchor)];
+}
+
+- (MGLHillshadeIlluminationAnchor)MGLHillshadeIlluminationAnchorValue {
+ MGLHillshadeIlluminationAnchor hillshadeIlluminationAnchor;
+ [self getValue:&hillshadeIlluminationAnchor];
+ return hillshadeIlluminationAnchor;
+}
+
+@end
diff --git a/platform/darwin/test/MGLHillshadeStyleLayerTests.mm b/platform/darwin/test/MGLHillshadeStyleLayerTests.mm
new file mode 100644
index 0000000000..283830ccb5
--- /dev/null
+++ b/platform/darwin/test/MGLHillshadeStyleLayerTests.mm
@@ -0,0 +1,345 @@
+// This file is generated.
+// Edit platform/darwin/scripts/generate-style-code.js, then run `make darwin-style-code`.
+
+#import "MGLStyleLayerTests.h"
+#import "../../darwin/src/NSDate+MGLAdditions.h"
+
+#import "MGLStyleLayer_Private.h"
+
+#include <mbgl/style/layers/hillshade_layer.hpp>
+#include <mbgl/style/transition_options.hpp>
+
+@interface MGLHillshadeLayerTests : MGLStyleLayerTests
+@end
+
+@implementation MGLHillshadeLayerTests
+
++ (NSString *)layerType {
+ return @"hillshade";
+}
+
+- (void)testPredicates {
+ MGLPointFeature *feature = [[MGLPointFeature alloc] init];
+ MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil];
+ MGLHillshadeStyleLayer *layer = [[MGLHillshadeStyleLayer 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];
+
+ MGLHillshadeStyleLayer *layer = [[MGLHillshadeStyleLayer alloc] initWithIdentifier:@"layerID" source:source];
+ XCTAssertNotEqual(layer.rawLayer, nullptr);
+ XCTAssertTrue(layer.rawLayer->is<mbgl::style::HillshadeLayer>());
+ auto rawLayer = layer.rawLayer->as<mbgl::style::HillshadeLayer>();
+
+ MGLTransition transitionTest = MGLTransitionMake(5, 4);
+
+
+ // hillshade-accent-color
+ {
+ XCTAssertTrue(rawLayer->getHillshadeAccentColor().isUndefined(),
+ @"hillshade-accent-color should be unset initially.");
+ MGLStyleValue<MGLColor *> *defaultStyleValue = layer.hillshadeAccentColor;
+
+ MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]];
+ layer.hillshadeAccentColor = constantStyleValue;
+ mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } };
+ XCTAssertEqual(rawLayer->getHillshadeAccentColor(), propertyValue,
+ @"Setting hillshadeAccentColor to a constant value should update hillshade-accent-color.");
+ XCTAssertEqualObjects(layer.hillshadeAccentColor, constantStyleValue,
+ @"hillshadeAccentColor should round-trip constant values.");
+
+ MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil];
+ layer.hillshadeAccentColor = functionStyleValue;
+
+ mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} };
+ propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHillshadeAccentColor(), propertyValue,
+ @"Setting hillshadeAccentColor to a camera function should update hillshade-accent-color.");
+ XCTAssertEqualObjects(layer.hillshadeAccentColor, functionStyleValue,
+ @"hillshadeAccentColor should round-trip camera functions.");
+
+
+
+ layer.hillshadeAccentColor = nil;
+ XCTAssertTrue(rawLayer->getHillshadeAccentColor().isUndefined(),
+ @"Unsetting hillshadeAccentColor should return hillshade-accent-color to the default value.");
+ XCTAssertEqualObjects(layer.hillshadeAccentColor, defaultStyleValue,
+ @"hillshadeAccentColor should return the default value after being unset.");
+
+ functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeAccentColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeAccentColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ // Transition property test
+ layer.hillshadeAccentColorTransition = transitionTest;
+ auto toptions = rawLayer->getHillshadeAccentColorTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition hillshadeAccentColorTransition = layer.hillshadeAccentColorTransition;
+ XCTAssertEqual(hillshadeAccentColorTransition.delay, transitionTest.delay);
+ XCTAssertEqual(hillshadeAccentColorTransition.duration, transitionTest.duration);
+ }
+
+ // hillshade-exaggeration
+ {
+ XCTAssertTrue(rawLayer->getHillshadeExaggeration().isUndefined(),
+ @"hillshade-exaggeration should be unset initially.");
+ MGLStyleValue<NSNumber *> *defaultStyleValue = layer.hillshadeExaggeration;
+
+ MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff];
+ layer.hillshadeExaggeration = constantStyleValue;
+ mbgl::style::PropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHillshadeExaggeration(), propertyValue,
+ @"Setting hillshadeExaggeration to a constant value should update hillshade-exaggeration.");
+ XCTAssertEqualObjects(layer.hillshadeExaggeration, constantStyleValue,
+ @"hillshadeExaggeration should round-trip constant values.");
+
+ MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil];
+ layer.hillshadeExaggeration = functionStyleValue;
+
+ mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} };
+ propertyValue = mbgl::style::CameraFunction<float> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHillshadeExaggeration(), propertyValue,
+ @"Setting hillshadeExaggeration to a camera function should update hillshade-exaggeration.");
+ XCTAssertEqualObjects(layer.hillshadeExaggeration, functionStyleValue,
+ @"hillshadeExaggeration should round-trip camera functions.");
+
+
+
+ layer.hillshadeExaggeration = nil;
+ XCTAssertTrue(rawLayer->getHillshadeExaggeration().isUndefined(),
+ @"Unsetting hillshadeExaggeration should return hillshade-exaggeration to the default value.");
+ XCTAssertEqualObjects(layer.hillshadeExaggeration, defaultStyleValue,
+ @"hillshadeExaggeration should return the default value after being unset.");
+
+ functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeExaggeration = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeExaggeration = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ // Transition property test
+ layer.hillshadeExaggerationTransition = transitionTest;
+ auto toptions = rawLayer->getHillshadeExaggerationTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition hillshadeExaggerationTransition = layer.hillshadeExaggerationTransition;
+ XCTAssertEqual(hillshadeExaggerationTransition.delay, transitionTest.delay);
+ XCTAssertEqual(hillshadeExaggerationTransition.duration, transitionTest.duration);
+ }
+
+ // hillshade-highlight-color
+ {
+ XCTAssertTrue(rawLayer->getHillshadeHighlightColor().isUndefined(),
+ @"hillshade-highlight-color should be unset initially.");
+ MGLStyleValue<MGLColor *> *defaultStyleValue = layer.hillshadeHighlightColor;
+
+ MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]];
+ layer.hillshadeHighlightColor = constantStyleValue;
+ mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } };
+ XCTAssertEqual(rawLayer->getHillshadeHighlightColor(), propertyValue,
+ @"Setting hillshadeHighlightColor to a constant value should update hillshade-highlight-color.");
+ XCTAssertEqualObjects(layer.hillshadeHighlightColor, constantStyleValue,
+ @"hillshadeHighlightColor should round-trip constant values.");
+
+ MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil];
+ layer.hillshadeHighlightColor = functionStyleValue;
+
+ mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} };
+ propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHillshadeHighlightColor(), propertyValue,
+ @"Setting hillshadeHighlightColor to a camera function should update hillshade-highlight-color.");
+ XCTAssertEqualObjects(layer.hillshadeHighlightColor, functionStyleValue,
+ @"hillshadeHighlightColor should round-trip camera functions.");
+
+
+
+ layer.hillshadeHighlightColor = nil;
+ XCTAssertTrue(rawLayer->getHillshadeHighlightColor().isUndefined(),
+ @"Unsetting hillshadeHighlightColor should return hillshade-highlight-color to the default value.");
+ XCTAssertEqualObjects(layer.hillshadeHighlightColor, defaultStyleValue,
+ @"hillshadeHighlightColor should return the default value after being unset.");
+
+ functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeHighlightColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeHighlightColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ // Transition property test
+ layer.hillshadeHighlightColorTransition = transitionTest;
+ auto toptions = rawLayer->getHillshadeHighlightColorTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition hillshadeHighlightColorTransition = layer.hillshadeHighlightColorTransition;
+ XCTAssertEqual(hillshadeHighlightColorTransition.delay, transitionTest.delay);
+ XCTAssertEqual(hillshadeHighlightColorTransition.duration, transitionTest.duration);
+ }
+
+ // hillshade-illumination-anchor
+ {
+ XCTAssertTrue(rawLayer->getHillshadeIlluminationAnchor().isUndefined(),
+ @"hillshade-illumination-anchor should be unset initially.");
+ MGLStyleValue<NSValue *> *defaultStyleValue = layer.hillshadeIlluminationAnchor;
+
+ MGLStyleValue<NSValue *> *constantStyleValue = [MGLStyleValue<NSValue *> valueWithRawValue:[NSValue valueWithMGLHillshadeIlluminationAnchor:MGLHillshadeIlluminationAnchorViewport]];
+ layer.hillshadeIlluminationAnchor = constantStyleValue;
+ mbgl::style::PropertyValue<mbgl::style::HillshadeIlluminationAnchorType> propertyValue = { mbgl::style::HillshadeIlluminationAnchorType::Viewport };
+ XCTAssertEqual(rawLayer->getHillshadeIlluminationAnchor(), propertyValue,
+ @"Setting hillshadeIlluminationAnchor to a constant value should update hillshade-illumination-anchor.");
+ XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, constantStyleValue,
+ @"hillshadeIlluminationAnchor should round-trip constant values.");
+
+ MGLStyleValue<NSValue *> * functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil];
+ layer.hillshadeIlluminationAnchor = functionStyleValue;
+
+ mbgl::style::IntervalStops<mbgl::style::HillshadeIlluminationAnchorType> intervalStops = { {{18, mbgl::style::HillshadeIlluminationAnchorType::Viewport}} };
+ propertyValue = mbgl::style::CameraFunction<mbgl::style::HillshadeIlluminationAnchorType> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHillshadeIlluminationAnchor(), propertyValue,
+ @"Setting hillshadeIlluminationAnchor to a camera function should update hillshade-illumination-anchor.");
+ XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, functionStyleValue,
+ @"hillshadeIlluminationAnchor should round-trip camera functions.");
+
+
+
+ layer.hillshadeIlluminationAnchor = nil;
+ XCTAssertTrue(rawLayer->getHillshadeIlluminationAnchor().isUndefined(),
+ @"Unsetting hillshadeIlluminationAnchor should return hillshade-illumination-anchor to the default value.");
+ XCTAssertEqualObjects(layer.hillshadeIlluminationAnchor, defaultStyleValue,
+ @"hillshadeIlluminationAnchor should return the default value after being unset.");
+
+ functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ functionStyleValue = [MGLStyleValue<NSValue *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationAnchor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ }
+
+ // hillshade-illumination-direction
+ {
+ XCTAssertTrue(rawLayer->getHillshadeIlluminationDirection().isUndefined(),
+ @"hillshade-illumination-direction should be unset initially.");
+ MGLStyleValue<NSNumber *> *defaultStyleValue = layer.hillshadeIlluminationDirection;
+
+ MGLStyleValue<NSNumber *> *constantStyleValue = [MGLStyleValue<NSNumber *> valueWithRawValue:@0xff];
+ layer.hillshadeIlluminationDirection = constantStyleValue;
+ mbgl::style::PropertyValue<float> propertyValue = { 0xff };
+ XCTAssertEqual(rawLayer->getHillshadeIlluminationDirection(), propertyValue,
+ @"Setting hillshadeIlluminationDirection to a constant value should update hillshade-illumination-direction.");
+ XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, constantStyleValue,
+ @"hillshadeIlluminationDirection should round-trip constant values.");
+
+ MGLStyleValue<NSNumber *> * functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil];
+ layer.hillshadeIlluminationDirection = functionStyleValue;
+
+ mbgl::style::IntervalStops<float> intervalStops = { {{18, 0xff}} };
+ propertyValue = mbgl::style::CameraFunction<float> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHillshadeIlluminationDirection(), propertyValue,
+ @"Setting hillshadeIlluminationDirection to a camera function should update hillshade-illumination-direction.");
+ XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, functionStyleValue,
+ @"hillshadeIlluminationDirection should round-trip camera functions.");
+
+
+
+ layer.hillshadeIlluminationDirection = nil;
+ XCTAssertTrue(rawLayer->getHillshadeIlluminationDirection().isUndefined(),
+ @"Unsetting hillshadeIlluminationDirection should return hillshade-illumination-direction to the default value.");
+ XCTAssertEqualObjects(layer.hillshadeIlluminationDirection, defaultStyleValue,
+ @"hillshadeIlluminationDirection should return the default value after being unset.");
+
+ functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationDirection = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ functionStyleValue = [MGLStyleValue<NSNumber *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeIlluminationDirection = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ // Transition property test
+ layer.hillshadeIlluminationDirectionTransition = transitionTest;
+ auto toptions = rawLayer->getHillshadeIlluminationDirectionTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition hillshadeIlluminationDirectionTransition = layer.hillshadeIlluminationDirectionTransition;
+ XCTAssertEqual(hillshadeIlluminationDirectionTransition.delay, transitionTest.delay);
+ XCTAssertEqual(hillshadeIlluminationDirectionTransition.duration, transitionTest.duration);
+ }
+
+ // hillshade-shadow-color
+ {
+ XCTAssertTrue(rawLayer->getHillshadeShadowColor().isUndefined(),
+ @"hillshade-shadow-color should be unset initially.");
+ MGLStyleValue<MGLColor *> *defaultStyleValue = layer.hillshadeShadowColor;
+
+ MGLStyleValue<MGLColor *> *constantStyleValue = [MGLStyleValue<MGLColor *> valueWithRawValue:[MGLColor redColor]];
+ layer.hillshadeShadowColor = constantStyleValue;
+ mbgl::style::PropertyValue<mbgl::Color> propertyValue = { { 1, 0, 0, 1 } };
+ XCTAssertEqual(rawLayer->getHillshadeShadowColor(), propertyValue,
+ @"Setting hillshadeShadowColor to a constant value should update hillshade-shadow-color.");
+ XCTAssertEqualObjects(layer.hillshadeShadowColor, constantStyleValue,
+ @"hillshadeShadowColor should round-trip constant values.");
+
+ MGLStyleValue<MGLColor *> * functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval cameraStops:@{@18: constantStyleValue} options:nil];
+ layer.hillshadeShadowColor = functionStyleValue;
+
+ mbgl::style::IntervalStops<mbgl::Color> intervalStops = { {{18, { 1, 0, 0, 1 }}} };
+ propertyValue = mbgl::style::CameraFunction<mbgl::Color> { intervalStops };
+
+ XCTAssertEqual(rawLayer->getHillshadeShadowColor(), propertyValue,
+ @"Setting hillshadeShadowColor to a camera function should update hillshade-shadow-color.");
+ XCTAssertEqualObjects(layer.hillshadeShadowColor, functionStyleValue,
+ @"hillshadeShadowColor should round-trip camera functions.");
+
+
+
+ layer.hillshadeShadowColor = nil;
+ XCTAssertTrue(rawLayer->getHillshadeShadowColor().isUndefined(),
+ @"Unsetting hillshadeShadowColor should return hillshade-shadow-color to the default value.");
+ XCTAssertEqualObjects(layer.hillshadeShadowColor, defaultStyleValue,
+ @"hillshadeShadowColor should return the default value after being unset.");
+
+ functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeIdentity sourceStops:nil attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeShadowColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ functionStyleValue = [MGLStyleValue<MGLColor *> valueWithInterpolationMode:MGLInterpolationModeInterval compositeStops:@{@18: constantStyleValue} attributeName:@"" options:nil];
+ XCTAssertThrowsSpecificNamed(layer.hillshadeShadowColor = functionStyleValue, NSException, NSInvalidArgumentException, @"MGLStyleValue should raise an exception if it is applied to a property that cannot support it");
+ // Transition property test
+ layer.hillshadeShadowColorTransition = transitionTest;
+ auto toptions = rawLayer->getHillshadeShadowColorTransition();
+ XCTAssert(toptions.delay && MGLTimeIntervalFromDuration(*toptions.delay) == transitionTest.delay);
+ XCTAssert(toptions.duration && MGLTimeIntervalFromDuration(*toptions.duration) == transitionTest.duration);
+
+ MGLTransition hillshadeShadowColorTransition = layer.hillshadeShadowColorTransition;
+ XCTAssertEqual(hillshadeShadowColorTransition.delay, transitionTest.delay);
+ XCTAssertEqual(hillshadeShadowColorTransition.duration, transitionTest.duration);
+ }
+}
+
+- (void)testPropertyNames {
+ [self testPropertyName:@"hillshade-accent-color" isBoolean:NO];
+ [self testPropertyName:@"hillshade-exaggeration" isBoolean:NO];
+ [self testPropertyName:@"hillshade-highlight-color" isBoolean:NO];
+ [self testPropertyName:@"hillshade-illumination-anchor" isBoolean:NO];
+ [self testPropertyName:@"hillshade-illumination-direction" isBoolean:NO];
+ [self testPropertyName:@"hillshade-shadow-color" isBoolean:NO];
+}
+
+- (void)testValueAdditions {
+ XCTAssertEqual([NSValue valueWithMGLHillshadeIlluminationAnchor:MGLHillshadeIlluminationAnchorMap].MGLHillshadeIlluminationAnchorValue, MGLHillshadeIlluminationAnchorMap);
+ XCTAssertEqual([NSValue valueWithMGLHillshadeIlluminationAnchor:MGLHillshadeIlluminationAnchorViewport].MGLHillshadeIlluminationAnchorValue, MGLHillshadeIlluminationAnchorViewport);
+}
+
+@end
diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp
index 8bb16993a5..ba504c1f9b 100644
--- a/platform/default/mbgl/storage/offline_download.cpp
+++ b/platform/default/mbgl/storage/offline_download.cpp
@@ -7,6 +7,7 @@
#include <mbgl/style/parser.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/sources/raster_source.hpp>
+#include <mbgl/style/sources/raster_dem_source.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/style/conversion/json.hpp>
@@ -110,6 +111,12 @@ OfflineRegionStatus OfflineDownload::getStatus() const {
handleTiledSource(rasterSource.getURLOrTileset(), rasterSource.getTileSize());
break;
}
+
+ case SourceType::RasterDEM: {
+ const auto& rasterDEMSource = *source->as<RasterDEMSource>();
+ handleTiledSource(rasterDEMSource.getURLOrTileset(), rasterDEMSource.getTileSize());
+ break;
+ }
case SourceType::GeoJSON: {
const auto& geojsonSource = *source->as<GeoJSONSource>();
@@ -195,6 +202,12 @@ void OfflineDownload::activateDownload() {
handleTiledSource(rasterSource.getURLOrTileset(), rasterSource.getTileSize());
break;
}
+
+ case SourceType::RasterDEM: {
+ const auto& rasterDEMSource = *source->as<RasterDEMSource>();
+ handleTiledSource(rasterDEMSource.getURLOrTileset(), rasterDEMSource.getTileSize());
+ break;
+ }
case SourceType::GeoJSON: {
const auto& geojsonSource = *source->as<GeoJSONSource>();
diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md
index 7eabfed777..00dba95419 100644
--- a/platform/ios/docs/guides/For Style Authors.md
+++ b/platform/ios/docs/guides/For Style Authors.md
@@ -190,6 +190,7 @@ In style JSON | In the SDK
`circle` | `MGLCircleStyleLayer`
`fill` | `MGLFillStyleLayer`
`fill-extrusion` | `MGLFillExtrusionStyleLayer`
+`hillshade` | `MGLHillshadeStyleLayer`
`line` | `MGLLineStyleLayer`
`raster` | `MGLRasterStyleLayer`
`symbol` | `MGLSymbolStyleLayer`
diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md
index 3cacc81376..9eeb159b75 100644
--- a/platform/macos/docs/guides/For Style Authors.md
+++ b/platform/macos/docs/guides/For Style Authors.md
@@ -177,6 +177,7 @@ In style JSON | In the SDK
`circle` | `MGLCircleStyleLayer`
`fill` | `MGLFillStyleLayer`
`fill-extrusion` | `MGLFillExtrusionStyleLayer`
+`hillshade` | `MGLHillshadeStyleLayer`
`line` | `MGLLineStyleLayer`
`raster` | `MGLRasterStyleLayer`
`symbol` | `MGLSymbolStyleLayer`
diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp
index b8c5e9cc88..5dd5e6b490 100644
--- a/platform/node/src/node_map.cpp
+++ b/platform/node/src/node_map.cpp
@@ -14,6 +14,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
@@ -800,6 +801,10 @@ struct SetFilterVisitor {
Nan::ThrowTypeError("layer doesn't support filters");
}
+ void operator()(mbgl::style::HillshadeLayer&) {
+ Nan::ThrowTypeError("layer doesn't support filters");
+ }
+
void operator()(mbgl::style::BackgroundLayer&) {
Nan::ThrowTypeError("layer doesn't support filters");
}
diff --git a/platform/node/test/ignores.json b/platform/node/test/ignores.json
index 6428bcf49f..1341fc86a1 100644
--- a/platform/node/test/ignores.json
+++ b/platform/node/test/ignores.json
@@ -62,21 +62,13 @@
"render-tests/text-pitch-scaling/line-half": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
"render-tests/video/default": "skip - https://github.com/mapbox/mapbox-gl-native/issues/601",
"render-tests/background-color/colorSpace-hcl": "needs issue",
- "render-tests/hillshade-accent-color/default": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-accent-color/literal": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-accent-color/zoom-function": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-highlight-color/default": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-highlight-color/literal": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-highlight-color/zoom-function": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-shadow-color/default": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-shadow-color/literal": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/hillshade-shadow-color/zoom-function": "skip - https://github.com/mapbox/mapbox-gl-native/pull/10642",
"render-tests/combinations/background-opaque--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/background-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/circle-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/fill-extrusion-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/fill-opaque--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/fill-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
+ "render-tests/combinations/hillshade-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/heatmap-translucent--background-opaque": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/heatmap-translucent--background-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
"render-tests/combinations/heatmap-translucent--circle-translucent": "https://github.com/mapbox/mapbox-gl-native/issues/10146",
@@ -91,35 +83,17 @@
"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/fill-opaque--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/fill-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--background-opaque": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--background-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--circle-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--fill-extrusion-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--fill-opaque": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--fill-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--heatmap-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--line-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--raster-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/hillshade-translucent--symbol-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/line-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/raster-translucent--hillshade-translucent": "https://github.com/mapbox/mapbox-gl-native/pull/10642",
- "render-tests/combinations/symbol-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",
"render-tests/combinations/fill-extrusion-translucent--background-translucent": "needs investigation",
"render-tests/combinations/fill-extrusion-translucent--circle-translucent": "needs investigation",
+ "render-tests/combinations/fill-extrusion-translucent--hillshade-translucent": "needs investigation",
"render-tests/combinations/fill-extrusion-translucent--fill-extrusion-translucent": "needs investigation",
"render-tests/combinations/fill-extrusion-translucent--fill-translucent": "needs investigation",
"render-tests/combinations/fill-extrusion-translucent--line-translucent": "needs investigation",
"render-tests/combinations/fill-extrusion-translucent--symbol-translucent": "needs investigation",
+ "render-tests/combinations/hillshade-translucent--fill-extrusion-translucent": "needs investigation",
"render-tests/combinations/fill-opaque--fill-extrusion-translucent": "needs investigation",
"render-tests/combinations/fill-translucent--fill-extrusion-translucent": "needs investigation",
"render-tests/combinations/line-translucent--fill-extrusion-translucent": "needs investigation",
diff --git a/src/mbgl/geometry/dem_data.cpp b/src/mbgl/geometry/dem_data.cpp
new file mode 100644
index 0000000000..78134dadc1
--- /dev/null
+++ b/src/mbgl/geometry/dem_data.cpp
@@ -0,0 +1,94 @@
+#include <mbgl/geometry/dem_data.hpp>
+#include <mbgl/math/clamp.hpp>
+
+namespace mbgl {
+
+DEMData::DEMData(const PremultipliedImage& _image):
+ dim(_image.size.height),
+ border(std::max<int32_t>(std::ceil(_image.size.height / 2), 1)),
+ stride(dim + 2 * border),
+ image({ static_cast<uint32_t>(stride), static_cast<uint32_t>(stride) }) {
+
+ if (_image.size.height != _image.size.width){
+ throw std::runtime_error("raster-dem tiles must be square.");
+ }
+
+ 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);
+ }
+ }
+
+ // in order to avoid flashing seams between tiles, here we are initially populating a 1px border of
+ // pixels around the image with the data of the nearest pixel from the image. this data is eventually
+ // replaced when the tile's neighboring tiles are loaded and the accurate data can be backfilled using
+ // DEMData#backfillBorder
+
+ for (int32_t x = 0; x < dim; x++) {
+ // left vertical border
+ set(-1, x, get(0, x));
+
+ // right vertical border
+ set(dim, x, get(dim - 1, x));
+
+ //left horizontal border
+ set(x, -1, get(x, 0));
+
+ // right horizontal border
+ set(x, dim, get(x, dim - 1));
+ }
+
+ // corners
+ set(-1, -1, get(0, 0));
+ set(dim, -1, get(dim - 1, 0));
+ set( -1, dim, get(0, dim - 1));
+ set(dim, dim, get(dim - 1, dim - 1));
+}
+
+// This function takes the DEMData from a neighboring tile and backfills the edge/corner
+// data in order to create a one pixel "buffer" of image data around the tile. This is
+// necessary because the hillshade formula calculates the dx/dz, dy/dz derivatives at each
+// pixel of the tile by querying the 8 surrounding pixels, and if we don't have the pixel
+// buffer we get seams at tile boundaries.
+void DEMData::backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy) {
+ auto& o = borderTileData;
+
+ // Tiles from the same source should always be of the same dimensions.
+ assert(dim == o.dim);
+
+ // We determine the pixel range to backfill based which corner/edge `borderTileData`
+ // represents. For example, dx = -1, dy = -1 represents the upper left corner of the
+ // base tile, so we only need to backfill one pixel at coordinates (-1, -1) of the tile
+ // image.
+ int32_t _xMin = dx * dim;
+ int32_t _xMax = dx * dim + dim;
+ int32_t _yMin = dy * dim;
+ int32_t _yMax = dy * dim + dim;
+
+ if (dx == -1) _xMin = _xMax - 1;
+ else if (dx == 1) _xMax = _xMin + 1;
+
+ if (dy == -1) _yMin = _yMax - 1;
+ else if (dy == 1) _yMax = _yMin + 1;
+
+ int32_t xMin = util::clamp(_xMin, -border, dim + border);
+ int32_t xMax = util::clamp(_xMax, -border, dim + border);
+
+ int32_t yMin = util::clamp(_yMin, -border, dim + border);
+ int32_t yMax = util::clamp(_yMax, -border, dim + border);
+
+ int32_t ox = -dx * dim;
+ int32_t oy = -dy * dim;
+
+ for (int32_t y = yMin; y < yMax; y++) {
+ for (int32_t x = xMin; x < xMax; x++) {
+ set(x, y, o.get(x + ox, y + oy));
+ }
+ }
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/geometry/dem_data.hpp b/src/mbgl/geometry/dem_data.hpp
new file mode 100644
index 0000000000..507a51661d
--- /dev/null
+++ b/src/mbgl/geometry/dem_data.hpp
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <mbgl/math/clamp.hpp>
+#include <mbgl/util/image.hpp>
+
+#include <memory>
+#include <array>
+#include <cassert>
+#include <vector>
+
+namespace mbgl {
+
+class DEMData {
+public:
+ DEMData(const PremultipliedImage& image);
+ 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) {
+ reinterpret_cast<int32_t*>(image.data.get())[idx(x, y)] = value + 65536;
+ }
+
+ int32_t get(const int32_t x, const int32_t y) const {
+ return reinterpret_cast<const int32_t*>(image.data.get())[idx(x, y)] - 65536;
+ }
+
+ const PremultipliedImage* getImage() const {
+ return &image;
+ }
+
+ const int32_t dim;
+ const int32_t border;
+ const int32_t stride;
+
+
+ private:
+ PremultipliedImage image;
+
+ size_t idx(const int32_t x, const int32_t y) const {
+ assert(x >= -border);
+ assert(x < dim + border);
+ assert(y >= -border);
+ assert(y < dim + border);
+ return (y + border) * stride + (x + border);
+ }
+
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/hillshade_prepare_program.cpp b/src/mbgl/programs/hillshade_prepare_program.cpp
new file mode 100644
index 0000000000..0c0446d3f5
--- /dev/null
+++ b/src/mbgl/programs/hillshade_prepare_program.cpp
@@ -0,0 +1,7 @@
+#include <mbgl/programs/hillshade_prepare_program.hpp>
+
+namespace mbgl {
+
+static_assert(sizeof(HillshadePrepareLayoutVertex) == 8, "expected HillshadeLayoutVertex size");
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/hillshade_prepare_program.hpp b/src/mbgl/programs/hillshade_prepare_program.hpp
new file mode 100644
index 0000000000..0f31a22df5
--- /dev/null
+++ b/src/mbgl/programs/hillshade_prepare_program.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <mbgl/programs/program.hpp>
+#include <mbgl/programs/attributes.hpp>
+#include <mbgl/programs/uniforms.hpp>
+#include <mbgl/shaders/hillshade_prepare.hpp>
+#include <mbgl/util/geometry.hpp>
+
+namespace mbgl {
+
+namespace uniforms {
+MBGL_DEFINE_UNIFORM_VECTOR(uint16_t, 2, u_dimension);
+} // namespace uniforms
+
+class HillshadePrepareProgram : public Program<
+ shaders::hillshade_prepare,
+ gl::Triangle,
+ gl::Attributes<
+ attributes::a_pos,
+ attributes::a_texture_pos>,
+ gl::Uniforms<
+ uniforms::u_matrix,
+ uniforms::u_dimension,
+ uniforms::u_zoom,
+ uniforms::u_image>,
+ style::Properties<>> {
+public:
+ using Program::Program;
+
+ static LayoutVertex layoutVertex(Point<int16_t> p, Point<uint16_t> t) {
+ return LayoutVertex {
+ {{
+ p.x,
+ p.y
+ }},
+ {{
+ t.x,
+ t.y
+ }}
+ };
+ }
+};
+
+using HillshadePrepareLayoutVertex = HillshadePrepareProgram::LayoutVertex;
+using HillshadePrepareAttributes = HillshadePrepareProgram::Attributes;
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/hillshade_program.cpp b/src/mbgl/programs/hillshade_program.cpp
new file mode 100644
index 0000000000..f054ad4b74
--- /dev/null
+++ b/src/mbgl/programs/hillshade_program.cpp
@@ -0,0 +1,7 @@
+#include <mbgl/programs/hillshade_program.hpp>
+
+namespace mbgl {
+
+static_assert(sizeof(HillshadeLayoutVertex) == 8, "expected HillshadeLayoutVertex size");
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/hillshade_program.hpp b/src/mbgl/programs/hillshade_program.hpp
new file mode 100644
index 0000000000..5f9b4d1c2f
--- /dev/null
+++ b/src/mbgl/programs/hillshade_program.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include <mbgl/programs/program.hpp>
+#include <mbgl/programs/attributes.hpp>
+#include <mbgl/programs/uniforms.hpp>
+#include <mbgl/shaders/hillshade.hpp>
+#include <mbgl/util/geometry.hpp>
+#include <mbgl/style/layers/hillshade_layer_properties.hpp>
+
+namespace mbgl {
+
+namespace uniforms {
+MBGL_DEFINE_UNIFORM_SCALAR(Color, u_shadow);
+MBGL_DEFINE_UNIFORM_SCALAR(Color, u_highlight);
+MBGL_DEFINE_UNIFORM_SCALAR(Color, u_accent);
+MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_light);
+MBGL_DEFINE_UNIFORM_VECTOR(float, 2, u_latrange);
+} // namespace uniforms
+
+class HillshadeProgram : public Program<
+ shaders::hillshade,
+ gl::Triangle,
+ gl::Attributes<
+ attributes::a_pos,
+ attributes::a_texture_pos>,
+ gl::Uniforms<
+ uniforms::u_matrix,
+ uniforms::u_image,
+ uniforms::u_highlight,
+ uniforms::u_shadow,
+ uniforms::u_accent,
+ uniforms::u_light,
+ uniforms::u_latrange>,
+ style::HillshadePaintProperties>{
+public:
+ using Program::Program;
+
+ static LayoutVertex layoutVertex(Point<int16_t> p, Point<uint16_t> t) {
+ return LayoutVertex {
+ {{
+ p.x,
+ p.y
+ }},
+ {{
+ t.x,
+ t.y
+ }}
+ };
+ }
+};
+
+using HillshadeLayoutVertex = HillshadeProgram::LayoutVertex;
+using HillshadeAttributes = HillshadeProgram::Attributes;
+
+} // namespace mbgl
diff --git a/src/mbgl/programs/programs.hpp b/src/mbgl/programs/programs.hpp
index a3891fafac..f533a6f633 100644
--- a/src/mbgl/programs/programs.hpp
+++ b/src/mbgl/programs/programs.hpp
@@ -6,6 +6,8 @@
#include <mbgl/programs/extrusion_texture_program.hpp>
#include <mbgl/programs/fill_program.hpp>
#include <mbgl/programs/fill_extrusion_program.hpp>
+#include <mbgl/programs/hillshade_program.hpp>
+#include <mbgl/programs/hillshade_prepare_program.hpp>
#include <mbgl/programs/line_program.hpp>
#include <mbgl/programs/raster_program.hpp>
#include <mbgl/programs/symbol_program.hpp>
@@ -28,6 +30,8 @@ public:
fillPattern(context, programParameters),
fillOutline(context, programParameters),
fillOutlinePattern(context, programParameters),
+ hillshade(context, programParameters),
+ hillshadePrepare(context, programParameters),
line(context, programParameters),
lineSDF(context, programParameters),
linePattern(context, programParameters),
@@ -51,6 +55,8 @@ public:
ProgramMap<FillPatternProgram> fillPattern;
ProgramMap<FillOutlineProgram> fillOutline;
ProgramMap<FillOutlinePatternProgram> fillOutlinePattern;
+ HillshadeProgram hillshade;
+ HillshadePrepareProgram hillshadePrepare;
ProgramMap<LineProgram> line;
ProgramMap<LineSDFProgram> lineSDF;
ProgramMap<LinePatternProgram> linePattern;
diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.cpp b/src/mbgl/renderer/buckets/hillshade_bucket.cpp
new file mode 100644
index 0000000000..8011681ee0
--- /dev/null
+++ b/src/mbgl/renderer/buckets/hillshade_bucket.cpp
@@ -0,0 +1,113 @@
+#include <mbgl/renderer/buckets/hillshade_bucket.hpp>
+#include <mbgl/renderer/layers/render_hillshade_layer.hpp>
+#include <mbgl/programs/hillshade_program.hpp>
+#include <mbgl/programs/hillshade_prepare_program.hpp>
+#include <mbgl/gl/context.hpp>
+
+namespace mbgl {
+
+using namespace style;
+
+HillshadeBucket::HillshadeBucket(PremultipliedImage&& image_): demdata(image_) {
+}
+
+HillshadeBucket::HillshadeBucket(DEMData&& demdata_) : demdata(std::move(demdata_)) {
+}
+
+const DEMData& HillshadeBucket::getDEMData() const {
+ return demdata;
+}
+
+DEMData& HillshadeBucket::getDEMData() {
+ return demdata;
+}
+
+void HillshadeBucket::upload(gl::Context& context) {
+ if (!hasData()) {
+ return;
+ }
+
+
+ const PremultipliedImage* image = demdata.getImage();
+ dem = context.createTexture(*image);
+
+ if (!segments.empty()) {
+ vertexBuffer = context.createVertexBuffer(std::move(vertices));
+ indexBuffer = context.createIndexBuffer(std::move(indices));
+ }
+ uploaded = true;
+}
+
+void HillshadeBucket::clear() {
+ vertexBuffer = {};
+ indexBuffer = {};
+ segments.clear();
+ vertices.clear();
+ indices.clear();
+
+ uploaded = false;
+}
+
+void HillshadeBucket::setMask(TileMask&& mask_) {
+ if (mask == mask_) {
+ return;
+ }
+
+ mask = std::move(mask_);
+ clear();
+
+ if (mask == TileMask{ { 0, 0, 0 } }) {
+ // We want to render the full tile, and keeping the segments/vertices/indices empty means
+ // using the global shared buffers for covering the entire tile.
+ return;
+ }
+
+ // Create a new segment so that we will upload (empty) buffers even when there is nothing to
+ // draw for this tile.
+ segments.emplace_back(0, 0);
+
+ constexpr const uint16_t vertexLength = 4;
+
+ // Create the vertex buffer for the specified tile mask.
+ for (const auto& id : mask) {
+ // Create a quad for every masked tile.
+ const int32_t vertexExtent = util::EXTENT >> id.z;
+
+ const Point<int16_t> tlVertex = { static_cast<int16_t>(id.x * vertexExtent),
+ static_cast<int16_t>(id.y * vertexExtent) };
+ const Point<int16_t> brVertex = { static_cast<int16_t>(tlVertex.x + vertexExtent),
+ static_cast<int16_t>(tlVertex.y + vertexExtent) };
+
+ if (segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::max()) {
+ // Move to a new segments because the old one can't hold the geometry.
+ segments.emplace_back(vertices.vertexSize(), indices.indexSize());
+ }
+
+ vertices.emplace_back(
+ HillshadeProgram::layoutVertex({ tlVertex.x, tlVertex.y }, { static_cast<uint16_t>(tlVertex.x), static_cast<uint16_t>(tlVertex.y) }));
+ vertices.emplace_back(
+ HillshadeProgram::layoutVertex({ brVertex.x, tlVertex.y }, { static_cast<uint16_t>(brVertex.x), static_cast<uint16_t>(tlVertex.y) }));
+ vertices.emplace_back(
+ HillshadeProgram::layoutVertex({ tlVertex.x, brVertex.y }, { static_cast<uint16_t>(tlVertex.x), static_cast<uint16_t>(brVertex.y) }));
+ vertices.emplace_back(
+ HillshadeProgram::layoutVertex({ brVertex.x, brVertex.y }, { static_cast<uint16_t>(brVertex.x), static_cast<uint16_t>(brVertex.y) }));
+
+ auto& segment = segments.back();
+ assert(segment.vertexLength <= std::numeric_limits<uint16_t>::max());
+ const uint16_t offset = segment.vertexLength;
+
+ // 0, 1, 2
+ // 1, 2, 3
+ indices.emplace_back(offset, offset + 1, offset + 2);
+ indices.emplace_back(offset + 1, offset + 2, offset + 3);
+
+ segment.vertexLength += vertexLength;
+ segment.indexLength += 6;
+ }
+}
+
+bool HillshadeBucket::hasData() const {
+ return demdata.getImage()->valid();
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/buckets/hillshade_bucket.hpp b/src/mbgl/renderer/buckets/hillshade_bucket.hpp
new file mode 100644
index 0000000000..3d9f6c61af
--- /dev/null
+++ b/src/mbgl/renderer/buckets/hillshade_bucket.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <mbgl/gl/index_buffer.hpp>
+#include <mbgl/gl/texture.hpp>
+#include <mbgl/gl/vertex_buffer.hpp>
+#include <mbgl/programs/hillshade_program.hpp>
+#include <mbgl/programs/hillshade_prepare_program.hpp>
+#include <mbgl/renderer/bucket.hpp>
+#include <mbgl/renderer/tile_mask.hpp>
+#include <mbgl/geometry/dem_data.hpp>
+#include <mbgl/util/image.hpp>
+#include <mbgl/util/mat4.hpp>
+#include <mbgl/util/optional.hpp>
+
+namespace mbgl {
+
+class HillshadeBucket : public Bucket {
+public:
+ HillshadeBucket(PremultipliedImage&&);
+ HillshadeBucket(std::shared_ptr<PremultipliedImage>);
+ HillshadeBucket(DEMData&&);
+
+
+ void upload(gl::Context&) override;
+ bool hasData() const override;
+
+ void clear();
+ void setMask(TileMask&&);
+
+ optional<gl::Texture> dem;
+ optional<gl::Texture> texture;
+
+ TileMask mask{ { 0, 0, 0 } };
+
+ const DEMData& getDEMData() const;
+ DEMData& getDEMData();
+
+ bool isPrepared() const {
+ return prepared;
+ }
+
+ void setPrepared (bool preparedState) {
+ prepared = preparedState;
+ }
+
+ // Raster-DEM Tile Sources use the default buffers from Painter
+ gl::VertexVector<HillshadeLayoutVertex> vertices;
+ gl::IndexVector<gl::Triangles> indices;
+ SegmentVector<HillshadeAttributes> segments;
+
+ optional<gl::VertexBuffer<HillshadeLayoutVertex>> vertexBuffer;
+ optional<gl::IndexBuffer<gl::Triangles>> indexBuffer;
+private:
+ DEMData demdata;
+ bool prepared = false;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp
new file mode 100644
index 0000000000..7a767522c0
--- /dev/null
+++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp
@@ -0,0 +1,158 @@
+#include <mbgl/renderer/layers/render_hillshade_layer.hpp>
+#include <mbgl/renderer/buckets/hillshade_bucket.hpp>
+#include <mbgl/renderer/render_tile.hpp>
+#include <mbgl/renderer/paint_parameters.hpp>
+#include <mbgl/renderer/render_static_data.hpp>
+#include <mbgl/programs/programs.hpp>
+#include <mbgl/programs/hillshade_program.hpp>
+#include <mbgl/programs/hillshade_prepare_program.hpp>
+#include <mbgl/tile/tile.hpp>
+#include <mbgl/style/layers/hillshade_layer_impl.hpp>
+#include <mbgl/util/geo.hpp>
+#include <mbgl/util/offscreen_texture.hpp>
+
+namespace mbgl {
+
+using namespace style;
+RenderHillshadeLayer::RenderHillshadeLayer(Immutable<style::HillshadeLayer::Impl> _impl)
+ : RenderLayer(style::LayerType::Hillshade, _impl),
+ unevaluated(impl().paint.untransitioned()) {
+}
+
+const style::HillshadeLayer::Impl& RenderHillshadeLayer::impl() const {
+ return static_cast<const style::HillshadeLayer::Impl&>(*baseImpl);
+}
+
+std::unique_ptr<Bucket> RenderHillshadeLayer::createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const {
+ assert(false);
+ return nullptr;
+}
+
+const std::array<float, 2> RenderHillshadeLayer::getLatRange(const UnwrappedTileID& id) {
+ const LatLng latlng0 = LatLng(id);
+ const LatLng latlng1 = LatLng(UnwrappedTileID(id.canonical.z, id.canonical.x, id.canonical.y + 1));
+ return {{ (float)latlng0.latitude(), (float)latlng1.latitude() }};
+}
+
+const std::array<float, 2> RenderHillshadeLayer::getLight(const PaintParameters& parameters){
+ float azimuthal = evaluated.get<HillshadeIlluminationDirection>() * util::DEG2RAD;
+ if (evaluated.get<HillshadeIlluminationAnchor>() == HillshadeIlluminationAnchorType::Viewport) azimuthal = azimuthal - parameters.state.getAngle();
+ return {{evaluated.get<HillshadeExaggeration>(), azimuthal}};
+}
+
+void RenderHillshadeLayer::transition(const TransitionParameters& parameters) {
+ unevaluated = impl().paint.transitioned(parameters, std::move(unevaluated));
+}
+
+void RenderHillshadeLayer::evaluate(const PropertyEvaluationParameters& parameters) {
+ evaluated = unevaluated.evaluate(parameters);
+ passes = (evaluated.get<style::HillshadeExaggeration >() > 0)
+ ? (RenderPass::Translucent | RenderPass::Pass3D)
+ : RenderPass::None;
+}
+
+bool RenderHillshadeLayer::hasTransition() const {
+ return unevaluated.hasTransition();
+}
+
+void RenderHillshadeLayer::render(PaintParameters& parameters, RenderSource*) {
+ if (parameters.pass != RenderPass::Translucent && parameters.pass != RenderPass::Pass3D)
+ return;
+
+ auto draw = [&] (const mat4& matrix,
+ const auto& vertexBuffer,
+ const auto& indexBuffer,
+ const auto& segments,
+ const UnwrappedTileID& id) {
+ parameters.programs.hillshade.draw(
+ parameters.context,
+ gl::Triangles(),
+ parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly),
+ gl::StencilMode::disabled(),
+ parameters.colorModeForRenderPass(),
+ HillshadeProgram::UniformValues {
+ uniforms::u_matrix::Value{ matrix },
+ uniforms::u_image::Value{ 0 },
+ uniforms::u_highlight::Value{ evaluated.get<HillshadeHighlightColor>() },
+ uniforms::u_shadow::Value{ evaluated.get<HillshadeShadowColor>() },
+ uniforms::u_accent::Value{ evaluated.get<HillshadeAccentColor>() },
+ uniforms::u_light::Value{ getLight(parameters) },
+ uniforms::u_latrange::Value{ getLatRange(id) },
+ },
+ vertexBuffer,
+ indexBuffer,
+ segments,
+ HillshadeProgram::PaintPropertyBinders { evaluated, 0 },
+ evaluated,
+ parameters.state.getZoom(),
+ getID()
+ );
+ };
+
+ mat4 mat;
+ matrix::ortho(mat, 0, util::EXTENT, -util::EXTENT, 0, 0, 1);
+ matrix::translate(mat, mat, 0, -util::EXTENT, 0);
+
+ for (const RenderTile& tile : renderTiles) {
+ assert(dynamic_cast<HillshadeBucket*>(tile.tile.getBucket(*baseImpl)));
+ HillshadeBucket& bucket = *reinterpret_cast<HillshadeBucket*>(tile.tile.getBucket(*baseImpl));
+ if (!bucket.hasData()){
+ continue;
+ }
+
+ if (!bucket.isPrepared() && parameters.pass == RenderPass::Pass3D) {
+ const uint16_t tilesize = bucket.getDEMData().dim;
+ OffscreenTexture view(parameters.context, { tilesize, tilesize });
+ view.bind();
+
+ parameters.context.bindTexture(*bucket.dem, 0, gl::TextureFilter::Nearest, gl::TextureMipMap::No, gl::TextureWrap::Clamp, gl::TextureWrap::Clamp);
+ const Properties<>::PossiblyEvaluated properties;
+
+ parameters.programs.hillshadePrepare.draw(
+ parameters.context,
+ gl::Triangles(),
+ parameters.depthModeForSublayer(0, gl::DepthMode::ReadOnly),
+ gl::StencilMode::disabled(),
+ parameters.colorModeForRenderPass(),
+ HillshadePrepareProgram::UniformValues {
+ 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_image::Value{ 0 }
+ },
+ parameters.staticData.rasterVertexBuffer,
+ parameters.staticData.quadTriangleIndexBuffer,
+ parameters.staticData.rasterSegments,
+ HillshadePrepareProgram::PaintPropertyBinders { properties, 0 },
+ properties,
+ parameters.state.getZoom(),
+ getID()
+ );
+ bucket.texture = std::move(view.getTexture());
+ bucket.setPrepared(true);
+ } else if (parameters.pass == RenderPass::Translucent) {
+ assert(bucket.texture);
+ parameters.context.bindTexture(*bucket.texture, 0, gl::TextureFilter::Linear, gl::TextureMipMap::No, gl::TextureWrap::Clamp, gl::TextureWrap::Clamp);
+
+ 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,
+ *bucket.vertexBuffer,
+ *bucket.indexBuffer,
+ bucket.segments,
+ tile.id);
+ } else {
+ // Draw the full tile.
+ draw(tile.matrix,
+ parameters.staticData.rasterVertexBuffer,
+ parameters.staticData.quadTriangleIndexBuffer,
+ parameters.staticData.rasterSegments,
+ tile.id);
+ }
+ }
+
+
+ }
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.hpp b/src/mbgl/renderer/layers/render_hillshade_layer.hpp
new file mode 100644
index 0000000000..e9b9db1ec3
--- /dev/null
+++ b/src/mbgl/renderer/layers/render_hillshade_layer.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <mbgl/renderer/render_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer_impl.hpp>
+#include <mbgl/style/layers/hillshade_layer_properties.hpp>
+#include <mbgl/tile/tile_id.hpp>
+
+namespace mbgl {
+
+class RenderHillshadeLayer: public RenderLayer {
+public:
+ RenderHillshadeLayer(Immutable<style::HillshadeLayer::Impl>);
+ ~RenderHillshadeLayer() final = default;
+
+ void transition(const TransitionParameters&) override;
+ void evaluate(const PropertyEvaluationParameters&) override;
+ bool hasTransition() const override;
+
+ void render(PaintParameters&, RenderSource*) override;
+
+ std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const RenderLayer*>&) const override;
+
+ // Paint properties
+ style::HillshadePaintProperties::Unevaluated unevaluated;
+ style::HillshadePaintProperties::PossiblyEvaluated evaluated;
+
+ const style::HillshadeLayer::Impl& impl() const;
+private:
+ const std::array<float, 2> getLatRange(const UnwrappedTileID& id);
+ const std::array<float, 2> getLight(const PaintParameters& parameters);
+};
+
+template <>
+inline bool RenderLayer::is<RenderHillshadeLayer>() const {
+ return type == style::LayerType::Hillshade;
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/render_layer.cpp b/src/mbgl/renderer/render_layer.cpp
index eb2b74ffe0..248905f834 100644
--- a/src/mbgl/renderer/render_layer.cpp
+++ b/src/mbgl/renderer/render_layer.cpp
@@ -4,6 +4,7 @@
#include <mbgl/renderer/layers/render_custom_layer.hpp>
#include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp>
#include <mbgl/renderer/layers/render_fill_layer.hpp>
+#include <mbgl/renderer/layers/render_hillshade_layer.hpp>
#include <mbgl/renderer/layers/render_line_layer.hpp>
#include <mbgl/renderer/layers/render_raster_layer.hpp>
#include <mbgl/renderer/layers/render_symbol_layer.hpp>
@@ -26,6 +27,8 @@ std::unique_ptr<RenderLayer> RenderLayer::create(Immutable<Layer::Impl> impl) {
return std::make_unique<RenderSymbolLayer>(staticImmutableCast<SymbolLayer::Impl>(impl));
case LayerType::Raster:
return std::make_unique<RenderRasterLayer>(staticImmutableCast<RasterLayer::Impl>(impl));
+ case LayerType::Hillshade:
+ return std::make_unique<RenderHillshadeLayer>(staticImmutableCast<HillshadeLayer::Impl>(impl));
case LayerType::Background:
return std::make_unique<RenderBackgroundLayer>(staticImmutableCast<BackgroundLayer::Impl>(impl));
case LayerType::Custom:
diff --git a/src/mbgl/renderer/render_source.cpp b/src/mbgl/renderer/render_source.cpp
index 6624bb7d96..d160eb16e3 100644
--- a/src/mbgl/renderer/render_source.cpp
+++ b/src/mbgl/renderer/render_source.cpp
@@ -2,6 +2,7 @@
#include <mbgl/renderer/render_source_observer.hpp>
#include <mbgl/renderer/sources/render_geojson_source.hpp>
#include <mbgl/renderer/sources/render_raster_source.hpp>
+#include <mbgl/renderer/sources/render_raster_dem_source.hpp>
#include <mbgl/renderer/sources/render_vector_source.hpp>
#include <mbgl/renderer/tile_parameters.hpp>
#include <mbgl/annotation/render_annotation_source.hpp>
@@ -19,6 +20,8 @@ std::unique_ptr<RenderSource> RenderSource::create(Immutable<Source::Impl> impl)
return std::make_unique<RenderVectorSource>(staticImmutableCast<VectorSource::Impl>(impl));
case SourceType::Raster:
return std::make_unique<RenderRasterSource>(staticImmutableCast<RasterSource::Impl>(impl));
+ case SourceType::RasterDEM:
+ return std::make_unique<RenderRasterDEMSource>(staticImmutableCast<RasterSource::Impl>(impl));
case SourceType::GeoJSON:
return std::make_unique<RenderGeoJSONSource>(staticImmutableCast<GeoJSONSource::Impl>(impl));
case SourceType::Video:
diff --git a/src/mbgl/renderer/render_source.hpp b/src/mbgl/renderer/render_source.hpp
index 8c84af4f1e..db88230e53 100644
--- a/src/mbgl/renderer/render_source.hpp
+++ b/src/mbgl/renderer/render_source.hpp
@@ -84,7 +84,7 @@ protected:
bool enabled = false;
- void onTileChanged(Tile&) final;
+ void onTileChanged(Tile&) override;
void onTileError(Tile&, std::exception_ptr) final;
};
diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp
index c38f1d56cb..61e7d17242 100644
--- a/src/mbgl/renderer/renderer_impl.cpp
+++ b/src/mbgl/renderer/renderer_impl.cpp
@@ -14,6 +14,7 @@
#include <mbgl/renderer/layers/render_background_layer.hpp>
#include <mbgl/renderer/layers/render_custom_layer.hpp>
#include <mbgl/renderer/layers/render_fill_extrusion_layer.hpp>
+#include <mbgl/renderer/layers/render_hillshade_layer.hpp>
#include <mbgl/renderer/style_diff.hpp>
#include <mbgl/renderer/query.hpp>
#include <mbgl/renderer/backend_scope.hpp>
@@ -289,7 +290,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) {
RenderLayer* layer = getRenderLayer(layerImpl->id);
assert(layer);
- if (!parameters.staticData.has3D && layer->is<RenderFillExtrusionLayer>()) {
+ if (!parameters.staticData.has3D && (layer->is<RenderFillExtrusionLayer>() || layer->is<RenderHillshadeLayer>())) {
parameters.staticData.has3D = true;
}
diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.cpp b/src/mbgl/renderer/sources/render_raster_dem_source.cpp
new file mode 100644
index 0000000000..76716518d7
--- /dev/null
+++ b/src/mbgl/renderer/sources/render_raster_dem_source.cpp
@@ -0,0 +1,165 @@
+#include <mbgl/renderer/sources/render_raster_dem_source.hpp>
+#include <mbgl/renderer/render_tile.hpp>
+#include <mbgl/tile/raster_dem_tile.hpp>
+#include <mbgl/algorithm/update_tile_masks.hpp>
+#include <mbgl/geometry/dem_data.hpp>
+#include <mbgl/renderer/buckets/hillshade_bucket.hpp>
+#include <iostream>
+
+namespace mbgl {
+
+using namespace style;
+
+RenderRasterDEMSource::RenderRasterDEMSource(Immutable<style::RasterSource::Impl> impl_)
+ : RenderSource(impl_) {
+ tilePyramid.setObserver(this);
+}
+
+const style::RasterSource::Impl& RenderRasterDEMSource::impl() const {
+ return static_cast<const style::RasterSource::Impl&>(*baseImpl);
+}
+
+bool RenderRasterDEMSource::isLoaded() const {
+ return tilePyramid.isLoaded();
+}
+
+void RenderRasterDEMSource::update(Immutable<style::Source::Impl> baseImpl_,
+ const std::vector<Immutable<Layer::Impl>>& layers,
+ const bool needsRendering,
+ const bool needsRelayout,
+ const TileParameters& parameters) {
+ std::swap(baseImpl, baseImpl_);
+
+ enabled = needsRendering;
+
+ optional<Tileset> tileset = impl().getTileset();
+
+ if (!tileset) {
+ return;
+ }
+
+ if (tileURLTemplates != tileset->tiles) {
+ tileURLTemplates = tileset->tiles;
+
+ // 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();
+ }
+
+ tilePyramid.update(layers,
+ needsRendering,
+ needsRelayout,
+ parameters,
+ SourceType::RasterDEM,
+ impl().getTileSize(),
+ tileset->zoomRange,
+ tileset->bounds,
+ [&] (const OverscaledTileID& tileID) {
+ return std::make_unique<RasterDEMTile>(tileID, parameters, *tileset);
+ });
+}
+
+void RenderRasterDEMSource::onTileChanged(Tile& tile){
+ RasterDEMTile& demtile = static_cast<RasterDEMTile&>(tile);
+
+ std::map<DEMTileNeighbors, DEMTileNeighbors> opposites = {
+ { DEMTileNeighbors::Left, DEMTileNeighbors::Right },
+ { DEMTileNeighbors::Right, DEMTileNeighbors::Left },
+ { DEMTileNeighbors::TopLeft, DEMTileNeighbors::BottomRight },
+ { DEMTileNeighbors::TopCenter, DEMTileNeighbors::BottomCenter },
+ { DEMTileNeighbors::TopRight, DEMTileNeighbors::BottomLeft },
+ { DEMTileNeighbors::BottomRight, DEMTileNeighbors::TopLeft },
+ { DEMTileNeighbors::BottomCenter, DEMTileNeighbors:: TopCenter },
+ { DEMTileNeighbors::BottomLeft, DEMTileNeighbors::TopRight }
+ };
+
+ if (tile.isRenderable() && demtile.neighboringTiles != DEMTileNeighbors::Complete) {
+ const CanonicalTileID canonical = tile.id.canonical;
+ const uint32_t dim = std::pow(2, canonical.z);
+ const uint32_t px = (canonical.x - 1 + dim) % dim;
+ const int pxw = canonical.x == 0 ? tile.id.wrap - 1 : tile.id.wrap;
+ const uint32_t nx = (canonical.x + 1 + dim) % dim;
+ const int nxw = (canonical.x + 1 == dim) ? tile.id.wrap + 1 : tile.id.wrap;
+
+ auto getNeighbor = [&] (DEMTileNeighbors mask){
+ if (mask == DEMTileNeighbors::Left){
+ return OverscaledTileID(tile.id.overscaledZ, pxw, canonical.z, px, canonical.y);
+ } else if (mask == DEMTileNeighbors::Right){
+ return OverscaledTileID(tile.id.overscaledZ, nxw, canonical.z, nx, canonical.y);
+ } else if (mask == DEMTileNeighbors::TopLeft){
+ return OverscaledTileID(tile.id.overscaledZ, pxw, canonical.z, px, canonical.y - 1);
+ } else if (mask == DEMTileNeighbors::TopCenter){
+ return OverscaledTileID(tile.id.overscaledZ, tile.id.wrap, canonical.z, canonical.x, canonical.y - 1);
+ } else if (mask == DEMTileNeighbors::TopRight){
+ return OverscaledTileID(tile.id.overscaledZ, nxw, canonical.z, nx, canonical.y - 1);
+ } else if (mask == DEMTileNeighbors::BottomLeft){
+ return OverscaledTileID(tile.id.overscaledZ, pxw, canonical.z, px, canonical.y + 1);
+ } else if (mask == DEMTileNeighbors::BottomCenter){
+ return OverscaledTileID(tile.id.overscaledZ, tile.id.wrap, canonical.z, canonical.x, canonical.y + 1);
+ } else if (mask == DEMTileNeighbors::BottomRight){
+ return OverscaledTileID(tile.id.overscaledZ, nxw, canonical.z, nx, canonical.y + 1);
+ } else{
+ throw std::runtime_error("mask is not a valid tile neighbor");
+ }
+ };
+
+ for (uint8_t i = 0; i < 8; i++) {
+ DEMTileNeighbors mask = DEMTileNeighbors(std::pow(2,i));
+ // only backfill if this neighbor has not been previously backfilled
+ if ((demtile.neighboringTiles & mask) != mask) {
+ OverscaledTileID neighborid = getNeighbor(mask);
+ Tile* renderableNeighbor = tilePyramid.getTile(neighborid);
+ if (renderableNeighbor != nullptr && renderableNeighbor->isRenderable()) {
+ RasterDEMTile& borderTile = static_cast<RasterDEMTile&>(*renderableNeighbor);
+ demtile.backfillBorder(borderTile, mask);
+
+ // if the border tile has not been backfilled by a previous instance of the main
+ // tile, backfill its corresponding neighbor as well.
+ const DEMTileNeighbors& borderMask = opposites[mask];
+ if ((borderTile.neighboringTiles & borderMask) != borderMask){
+ borderTile.backfillBorder(demtile, borderMask);
+ }
+ }
+ }
+ }
+ }
+ RenderSource::onTileChanged(tile);
+}
+
+void RenderRasterDEMSource::startRender(PaintParameters& parameters) {
+ algorithm::updateTileMasks(tilePyramid.getRenderTiles());
+ tilePyramid.startRender(parameters);
+}
+
+void RenderRasterDEMSource::finishRender(PaintParameters& parameters) {
+ tilePyramid.finishRender(parameters);
+}
+
+std::vector<std::reference_wrapper<RenderTile>> RenderRasterDEMSource::getRenderTiles() {
+ return tilePyramid.getRenderTiles();
+}
+
+std::unordered_map<std::string, std::vector<Feature>>
+RenderRasterDEMSource::queryRenderedFeatures(const ScreenLineString&,
+ const TransformState&,
+ const std::vector<const RenderLayer*>&,
+ const RenderedQueryOptions&,
+ const CollisionIndex& ) const {
+ return std::unordered_map<std::string, std::vector<Feature>> {};
+}
+
+std::vector<Feature> RenderRasterDEMSource::querySourceFeatures(const SourceQueryOptions&) const {
+ return {};
+}
+
+void RenderRasterDEMSource::onLowMemory() {
+ tilePyramid.onLowMemory();
+}
+
+void RenderRasterDEMSource::dumpDebugLogs() const {
+ tilePyramid.dumpDebugLogs();
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/sources/render_raster_dem_source.hpp b/src/mbgl/renderer/sources/render_raster_dem_source.hpp
new file mode 100644
index 0000000000..b6b8bf977c
--- /dev/null
+++ b/src/mbgl/renderer/sources/render_raster_dem_source.hpp
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <mbgl/renderer/render_source.hpp>
+#include <mbgl/renderer/tile_pyramid.hpp>
+#include <mbgl/style/sources/raster_source_impl.hpp>
+
+namespace mbgl {
+
+class RenderRasterDEMSource : public RenderSource {
+public:
+ RenderRasterDEMSource(Immutable<style::RasterSource::Impl>);
+
+ bool isLoaded() const final;
+
+ void update(Immutable<style::Source::Impl>,
+ const std::vector<Immutable<style::Layer::Impl>>&,
+ bool needsRendering,
+ bool needsRelayout,
+ const TileParameters&) final;
+
+ void startRender(PaintParameters&) final;
+ void finishRender(PaintParameters&) final;
+
+ std::vector<std::reference_wrapper<RenderTile>> getRenderTiles() final;
+
+ std::unordered_map<std::string, std::vector<Feature>>
+ queryRenderedFeatures(const ScreenLineString& geometry,
+ const TransformState& transformState,
+ const std::vector<const RenderLayer*>& layers,
+ const RenderedQueryOptions& options,
+ const CollisionIndex& collisionIndex) const final;
+
+ std::vector<Feature>
+ querySourceFeatures(const SourceQueryOptions&) const final;
+
+ void onLowMemory() final;
+ void dumpDebugLogs() const final;
+
+private:
+ const style::RasterSource::Impl& impl() const;
+
+ TilePyramid tilePyramid;
+ optional<std::vector<std::string>> tileURLTemplates;
+
+protected:
+ void onTileChanged(Tile&) final;
+};
+
+template <>
+inline bool RenderSource::is<RenderRasterDEMSource>() const {
+ return baseImpl->type == style::SourceType::RasterDEM;
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp
index f6eb62f655..07239b7a1c 100644
--- a/src/mbgl/renderer/tile_pyramid.cpp
+++ b/src/mbgl/renderer/tile_pyramid.cpp
@@ -56,6 +56,11 @@ std::vector<std::reference_wrapper<RenderTile>> TilePyramid::getRenderTiles() {
return { renderTiles.begin(), renderTiles.end() };
}
+Tile* TilePyramid::getTile(const OverscaledTileID& tileID){
+ auto it = tiles.find(tileID);
+ return it == tiles.end() ? cache.get(tileID) : it->second.get();
+}
+
void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layers,
const bool needsRendering,
const bool needsRelayout,
@@ -147,7 +152,7 @@ void TilePyramid::update(const std::vector<Immutable<style::Layer::Impl>>& layer
if (tileRange && !tileRange->contains(tileID.canonical)) {
return nullptr;
}
- std::unique_ptr<Tile> tile = cache.get(tileID);
+ std::unique_ptr<Tile> tile = cache.pop(tileID);
if (!tile) {
tile = createTile(tileID);
if (tile) {
diff --git a/src/mbgl/renderer/tile_pyramid.hpp b/src/mbgl/renderer/tile_pyramid.hpp
index 3755cee06d..ad3f91bf88 100644
--- a/src/mbgl/renderer/tile_pyramid.hpp
+++ b/src/mbgl/renderer/tile_pyramid.hpp
@@ -47,6 +47,7 @@ public:
void finishRender(PaintParameters&);
std::vector<std::reference_wrapper<RenderTile>> getRenderTiles();
+ Tile* getTile(const OverscaledTileID&);
std::unordered_map<std::string, std::vector<Feature>>
queryRenderedFeatures(const ScreenLineString& geometry,
diff --git a/src/mbgl/shaders/hillshade.cpp b/src/mbgl/shaders/hillshade.cpp
new file mode 100644
index 0000000000..4083faa4b4
--- /dev/null
+++ b/src/mbgl/shaders/hillshade.cpp
@@ -0,0 +1,80 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#include <mbgl/shaders/hillshade.hpp>
+
+namespace mbgl {
+namespace shaders {
+
+const char* hillshade::name = "hillshade";
+const char* hillshade::vertexSource = R"MBGL_SHADER(
+uniform mat4 u_matrix;
+
+attribute vec2 a_pos;
+attribute vec2 a_texture_pos;
+
+varying vec2 v_pos;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ v_pos = a_texture_pos / 8192.0;
+}
+
+)MBGL_SHADER";
+const char* hillshade::fragmentSource = R"MBGL_SHADER(
+uniform sampler2D u_image;
+varying vec2 v_pos;
+
+uniform vec2 u_latrange;
+uniform vec2 u_light;
+uniform vec4 u_shadow;
+uniform vec4 u_highlight;
+uniform vec4 u_accent;
+
+#define PI 3.141592653589793
+
+void main() {
+ vec4 pixel = texture2D(u_image, v_pos);
+
+ vec2 deriv = ((pixel.rg * 2.0) - 1.0);
+
+ // We divide the slope by a scale factor based on the cosin of the pixel's approximate latitude
+ // to account for mercator projection distortion. see #4807 for details
+ float scaleFactor = cos(radians((u_latrange[0] - u_latrange[1]) * (1.0 - v_pos.y) + u_latrange[1]));
+ // We also multiply the slope by an arbitrary z-factor of 1.25
+ float slope = atan(1.25 * length(deriv) / scaleFactor);
+ float aspect = deriv.x != 0.0 ? atan(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0);
+
+ float intensity = u_light.x;
+ // We add PI to make this property match the global light object, which adds PI/2 to the light's azimuthal
+ // position property to account for 0deg corresponding to north/the top of the viewport in the style spec
+ // and the original shader was written to accept (-illuminationDirection - 90) as the azimuthal.
+ float azimuth = u_light.y + PI;
+
+ // We scale the slope exponentially based on intensity, using a calculation similar to
+ // the exponential interpolation function in the style spec:
+ // https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/expression/definitions/interpolate.js#L217-L228
+ // so that higher intensity values create more opaque hillshading.
+ float base = 1.875 - intensity * 1.75;
+ float maxValue = 0.5 * PI;
+ float scaledSlope = intensity != 0.5 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope;
+
+ // The accent color is calculated with the cosine of the slope while the shade color is calculated with the sine
+ // so that the accent color's rate of change eases in while the shade color's eases out.
+ float accent = cos(scaledSlope);
+ // We multiply both the accent and shade color by a clamped intensity value
+ // so that intensities >= 0.5 do not additionally affect the color values
+ // while intensity values < 0.5 make the overall color more transparent.
+ vec4 accent_color = (1.0 - accent) * u_accent * clamp(intensity * 2.0, 0.0, 1.0);
+ float shade = abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0);
+ vec4 shade_color = mix(u_shadow, u_highlight, shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0);
+ gl_FragColor = accent_color * (1.0 - shade_color.a) + shade_color;
+
+#ifdef OVERDRAW_INSPECTOR
+ gl_FragColor = vec4(1.0);
+#endif
+}
+
+)MBGL_SHADER";
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/hillshade.hpp b/src/mbgl/shaders/hillshade.hpp
new file mode 100644
index 0000000000..a4a27cb595
--- /dev/null
+++ b/src/mbgl/shaders/hillshade.hpp
@@ -0,0 +1,16 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#pragma once
+
+namespace mbgl {
+namespace shaders {
+
+class hillshade {
+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
new file mode 100644
index 0000000000..733658435e
--- /dev/null
+++ b/src/mbgl/shaders/hillshade_prepare.cpp
@@ -0,0 +1,99 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#include <mbgl/shaders/hillshade_prepare.hpp>
+
+namespace mbgl {
+namespace shaders {
+
+const char* hillshade_prepare::name = "hillshade_prepare";
+const char* hillshade_prepare::vertexSource = R"MBGL_SHADER(
+uniform mat4 u_matrix;
+
+attribute vec2 a_pos;
+attribute vec2 a_texture_pos;
+
+varying vec2 v_pos;
+
+void main() {
+ gl_Position = u_matrix * vec4(a_pos, 0, 1);
+ v_pos = (a_texture_pos / 8192.0) / 2.0 + 0.25;
+}
+
+)MBGL_SHADER";
+const char* hillshade_prepare::fragmentSource = R"MBGL_SHADER(
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform sampler2D u_image;
+varying vec2 v_pos;
+uniform vec2 u_dimension;
+uniform float u_zoom;
+
+float getElevation(vec2 coord, float bias) {
+ // Convert encoded elevation value to meters
+ vec4 data = texture2D(u_image, coord) * 255.0;
+ return (data.r + data.g * 256.0 + data.b * 256.0 * 256.0) / 4.0;
+}
+
+void main() {
+ vec2 epsilon = 1.0 / u_dimension;
+
+ // queried pixels:
+ // +-----------+
+ // | | | |
+ // | a | b | c |
+ // | | | |
+ // +-----------+
+ // | | | |
+ // | d | e | f |
+ // | | | |
+ // +-----------+
+ // | | | |
+ // | g | h | i |
+ // | | | |
+ // +-----------+
+
+ float a = getElevation(v_pos + vec2(-epsilon.x, -epsilon.y), 0.0);
+ float b = getElevation(v_pos + vec2(0, -epsilon.y), 0.0);
+ float c = getElevation(v_pos + vec2(epsilon.x, -epsilon.y), 0.0);
+ float d = getElevation(v_pos + vec2(-epsilon.x, 0), 0.0);
+ float e = getElevation(v_pos, 0.0);
+ float f = getElevation(v_pos + vec2(epsilon.x, 0), 0.0);
+ float g = getElevation(v_pos + vec2(-epsilon.x, epsilon.y), 0.0);
+ float h = getElevation(v_pos + vec2(0, epsilon.y), 0.0);
+ float i = getElevation(v_pos + vec2(epsilon.x, epsilon.y), 0.0);
+
+ // here we divide the x and y slopes by 8 * pixel size
+ // where pixel size (aka meters/pixel) is:
+ // circumference of the world / (pixels per tile * number of tiles)
+ // which is equivalent to: 8 * 40075016.6855785 / (512 * pow(2, u_zoom))
+ // 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
+ // 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);
+
+ gl_FragColor = clamp(vec4(
+ deriv.x / 2.0 + 0.5,
+ deriv.y / 2.0 + 0.5,
+ 1.0,
+ 1.0), 0.0, 1.0);
+
+#ifdef OVERDRAW_INSPECTOR
+ gl_FragColor = vec4(1.0);
+#endif
+}
+
+)MBGL_SHADER";
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/shaders/hillshade_prepare.hpp b/src/mbgl/shaders/hillshade_prepare.hpp
new file mode 100644
index 0000000000..c38b4a0d19
--- /dev/null
+++ b/src/mbgl/shaders/hillshade_prepare.hpp
@@ -0,0 +1,16 @@
+// NOTE: DO NOT CHANGE THIS FILE. IT IS AUTOMATICALLY GENERATED.
+
+#pragma once
+
+namespace mbgl {
+namespace shaders {
+
+class hillshade_prepare {
+public:
+ static const char* name;
+ static const char* vertexSource;
+ static const char* fragmentSource;
+};
+
+} // namespace shaders
+} // namespace mbgl
diff --git a/src/mbgl/style/conversion/layer.cpp b/src/mbgl/style/conversion/layer.cpp
index 0ca582f8dc..8f624e3324 100644
--- a/src/mbgl/style/conversion/layer.cpp
+++ b/src/mbgl/style/conversion/layer.cpp
@@ -6,6 +6,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
@@ -96,6 +97,23 @@ static optional<std::unique_ptr<Layer>> convertRasterLayer(const std::string& id
return { std::make_unique<RasterLayer>(id, *source) };
}
+static optional<std::unique_ptr<Layer>> convertHillshadeLayer(const std::string& id, const Convertible& value, Error& error) {
+ auto sourceValue = objectMember(value, "source");
+ if (!sourceValue) {
+ error = { "layer must have a source" };
+ return {};
+ }
+
+ optional<std::string> source = toString(*sourceValue);
+ if (!source) {
+ error = { "layer source must be a string" };
+ return {};
+ }
+
+ return { std::make_unique<HillshadeLayer>(id, *source) };
+}
+
+
static optional<std::unique_ptr<Layer>> convertBackgroundLayer(const std::string& id, const Convertible&, Error&) {
return { std::make_unique<BackgroundLayer>(id) };
}
@@ -144,6 +162,8 @@ optional<std::unique_ptr<Layer>> Converter<std::unique_ptr<Layer>>::operator()(c
converted = convertVectorLayer<SymbolLayer>(*id, value, error);
} else if (*type == "raster") {
converted = convertRasterLayer(*id, value, error);
+ } else if (*type == "hillshade") {
+ converted = convertHillshadeLayer(*id, value, error);
} else if (*type == "background") {
converted = convertBackgroundLayer(*id, value, error);
} else {
diff --git a/src/mbgl/style/conversion/make_property_setters.hpp b/src/mbgl/style/conversion/make_property_setters.hpp
index 18370df636..adfcc4dd61 100644
--- a/src/mbgl/style/conversion/make_property_setters.hpp
+++ b/src/mbgl/style/conversion/make_property_setters.hpp
@@ -10,6 +10,7 @@
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/fill_extrusion_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layers/background_layer.hpp>
#include <unordered_map>
@@ -70,6 +71,7 @@ inline auto makeLayoutPropertySetters() {
+
return result;
}
@@ -194,6 +196,19 @@ inline auto makePaintPropertySetters() {
result["raster-fade-duration"] = &setProperty<RasterLayer, PropertyValue<float>, &RasterLayer::setRasterFadeDuration>;
result["raster-fade-duration-transition"] = &setTransition<RasterLayer, &RasterLayer::setRasterFadeDurationTransition>;
+ result["hillshade-illumination-direction"] = &setProperty<HillshadeLayer, PropertyValue<float>, &HillshadeLayer::setHillshadeIlluminationDirection>;
+ result["hillshade-illumination-direction-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeIlluminationDirectionTransition>;
+ result["hillshade-illumination-anchor"] = &setProperty<HillshadeLayer, PropertyValue<HillshadeIlluminationAnchorType>, &HillshadeLayer::setHillshadeIlluminationAnchor>;
+ result["hillshade-illumination-anchor-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeIlluminationAnchorTransition>;
+ result["hillshade-exaggeration"] = &setProperty<HillshadeLayer, PropertyValue<float>, &HillshadeLayer::setHillshadeExaggeration>;
+ result["hillshade-exaggeration-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeExaggerationTransition>;
+ result["hillshade-shadow-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeShadowColor>;
+ result["hillshade-shadow-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeShadowColorTransition>;
+ result["hillshade-highlight-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeHighlightColor>;
+ result["hillshade-highlight-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeHighlightColorTransition>;
+ result["hillshade-accent-color"] = &setProperty<HillshadeLayer, PropertyValue<Color>, &HillshadeLayer::setHillshadeAccentColor>;
+ result["hillshade-accent-color-transition"] = &setTransition<HillshadeLayer, &HillshadeLayer::setHillshadeAccentColorTransition>;
+
result["background-color"] = &setProperty<BackgroundLayer, PropertyValue<Color>, &BackgroundLayer::setBackgroundColor>;
result["background-color-transition"] = &setTransition<BackgroundLayer, &BackgroundLayer::setBackgroundColorTransition>;
result["background-pattern"] = &setProperty<BackgroundLayer, PropertyValue<std::string>, &BackgroundLayer::setBackgroundPattern>;
diff --git a/src/mbgl/style/conversion/source.cpp b/src/mbgl/style/conversion/source.cpp
index c10d0babcf..670f50c041 100644
--- a/src/mbgl/style/conversion/source.cpp
+++ b/src/mbgl/style/conversion/source.cpp
@@ -5,6 +5,7 @@
#include <mbgl/style/conversion/tileset.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/sources/raster_source.hpp>
+#include <mbgl/style/sources/raster_dem_source.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/util/geo.hpp>
@@ -55,6 +56,28 @@ static optional<std::unique_ptr<Source>> convertRasterSource(const std::string&
return { std::make_unique<RasterSource>(id, std::move(*urlOrTileset), tileSize) };
}
+static optional<std::unique_ptr<Source>> convertRasterDEMSource(const std::string& id,
+ const Convertible& value,
+ Error& error) {
+ optional<variant<std::string, Tileset>> urlOrTileset = convertURLOrTileset(value, error);
+ if (!urlOrTileset) {
+ return {};
+ }
+
+ uint16_t tileSize = util::tileSize;
+ auto tileSizeValue = objectMember(value, "tileSize");
+ if (tileSizeValue) {
+ optional<float> size = toNumber(*tileSizeValue);
+ if (!size || *size < 0 || *size > std::numeric_limits<uint16_t>::max()) {
+ error = { "invalid tileSize" };
+ return {};
+ }
+ tileSize = *size;
+ }
+
+ return { std::make_unique<RasterDEMSource>(id, std::move(*urlOrTileset), tileSize) };
+}
+
static optional<std::unique_ptr<Source>> convertVectorSource(const std::string& id,
const Convertible& value,
Error& error) {
@@ -155,9 +178,11 @@ optional<std::unique_ptr<Source>> Converter<std::unique_ptr<Source>>::operator()
error = { "source type must be a string" };
return {};
}
-
+ const std::string tname = *type;
if (*type == "raster") {
return convertRasterSource(id, value, error);
+ } else if (*type == "raster-dem") {
+ return convertRasterDEMSource(id, value, error);
} else if (*type == "vector") {
return convertVectorSource(id, value, error);
} else if (*type == "geojson") {
diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp
index ef4769df02..faa44e78aa 100644
--- a/src/mbgl/style/expression/value.cpp
+++ b/src/mbgl/style/expression/value.cpp
@@ -305,6 +305,10 @@ template type::Type valueTypeToExpressionType<TranslateAnchorType>();
template optional<TranslateAnchorType> fromExpressionValue<TranslateAnchorType>(const Value&);
template Value toExpressionValue(const TranslateAnchorType&);
+template type::Type valueTypeToExpressionType<HillshadeIlluminationAnchorType>();
+template optional<HillshadeIlluminationAnchorType> fromExpressionValue<HillshadeIlluminationAnchorType>(const Value&);
+template Value toExpressionValue(const HillshadeIlluminationAnchorType&);
+
template type::Type valueTypeToExpressionType<LightAnchorType>();
template optional<LightAnchorType> fromExpressionValue<LightAnchorType>(const Value&);
template Value toExpressionValue(const LightAnchorType&);
diff --git a/src/mbgl/style/layers/hillshade_layer.cpp b/src/mbgl/style/layers/hillshade_layer.cpp
new file mode 100644
index 0000000000..ea736af1ad
--- /dev/null
+++ b/src/mbgl/style/layers/hillshade_layer.cpp
@@ -0,0 +1,238 @@
+// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
+
+#include <mbgl/style/layers/hillshade_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer_impl.hpp>
+#include <mbgl/style/layer_observer.hpp>
+
+namespace mbgl {
+namespace style {
+
+HillshadeLayer::HillshadeLayer(const std::string& layerID, const std::string& sourceID)
+ : Layer(makeMutable<Impl>(LayerType::Hillshade, layerID, sourceID)) {
+}
+
+HillshadeLayer::HillshadeLayer(Immutable<Impl> impl_)
+ : Layer(std::move(impl_)) {
+}
+
+HillshadeLayer::~HillshadeLayer() = default;
+
+const HillshadeLayer::Impl& HillshadeLayer::impl() const {
+ return static_cast<const Impl&>(*baseImpl);
+}
+
+Mutable<HillshadeLayer::Impl> HillshadeLayer::mutableImpl() const {
+ return makeMutable<Impl>(impl());
+}
+
+std::unique_ptr<Layer> HillshadeLayer::cloneRef(const std::string& id_) const {
+ auto impl_ = mutableImpl();
+ impl_->id = id_;
+ impl_->paint = HillshadePaintProperties::Transitionable();
+ return std::make_unique<HillshadeLayer>(std::move(impl_));
+}
+
+void HillshadeLayer::Impl::stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const {
+}
+
+// Source
+
+const std::string& HillshadeLayer::getSourceID() const {
+ return impl().source;
+}
+
+
+// Visibility
+
+void HillshadeLayer::setVisibility(VisibilityType value) {
+ if (value == getVisibility())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->visibility = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+// Zoom range
+
+void HillshadeLayer::setMinZoom(float minZoom) {
+ auto impl_ = mutableImpl();
+ impl_->minZoom = minZoom;
+ baseImpl = std::move(impl_);
+}
+
+void HillshadeLayer::setMaxZoom(float maxZoom) {
+ auto impl_ = mutableImpl();
+ impl_->maxZoom = maxZoom;
+ baseImpl = std::move(impl_);
+}
+
+// Layout properties
+
+
+// Paint properties
+
+PropertyValue<float> HillshadeLayer::getDefaultHillshadeIlluminationDirection() {
+ return { 335 };
+}
+
+PropertyValue<float> HillshadeLayer::getHillshadeIlluminationDirection() const {
+ return impl().paint.template get<HillshadeIlluminationDirection>().value;
+}
+
+void HillshadeLayer::setHillshadeIlluminationDirection(PropertyValue<float> value) {
+ if (value == getHillshadeIlluminationDirection())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeIlluminationDirection>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HillshadeLayer::setHillshadeIlluminationDirectionTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeIlluminationDirection>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HillshadeLayer::getHillshadeIlluminationDirectionTransition() const {
+ return impl().paint.template get<HillshadeIlluminationDirection>().options;
+}
+
+PropertyValue<HillshadeIlluminationAnchorType> HillshadeLayer::getDefaultHillshadeIlluminationAnchor() {
+ return { HillshadeIlluminationAnchorType::Viewport };
+}
+
+PropertyValue<HillshadeIlluminationAnchorType> HillshadeLayer::getHillshadeIlluminationAnchor() const {
+ return impl().paint.template get<HillshadeIlluminationAnchor>().value;
+}
+
+void HillshadeLayer::setHillshadeIlluminationAnchor(PropertyValue<HillshadeIlluminationAnchorType> value) {
+ if (value == getHillshadeIlluminationAnchor())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeIlluminationAnchor>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HillshadeLayer::setHillshadeIlluminationAnchorTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeIlluminationAnchor>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HillshadeLayer::getHillshadeIlluminationAnchorTransition() const {
+ return impl().paint.template get<HillshadeIlluminationAnchor>().options;
+}
+
+PropertyValue<float> HillshadeLayer::getDefaultHillshadeExaggeration() {
+ return { 0.5 };
+}
+
+PropertyValue<float> HillshadeLayer::getHillshadeExaggeration() const {
+ return impl().paint.template get<HillshadeExaggeration>().value;
+}
+
+void HillshadeLayer::setHillshadeExaggeration(PropertyValue<float> value) {
+ if (value == getHillshadeExaggeration())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeExaggeration>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HillshadeLayer::setHillshadeExaggerationTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeExaggeration>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HillshadeLayer::getHillshadeExaggerationTransition() const {
+ return impl().paint.template get<HillshadeExaggeration>().options;
+}
+
+PropertyValue<Color> HillshadeLayer::getDefaultHillshadeShadowColor() {
+ return { Color::black() };
+}
+
+PropertyValue<Color> HillshadeLayer::getHillshadeShadowColor() const {
+ return impl().paint.template get<HillshadeShadowColor>().value;
+}
+
+void HillshadeLayer::setHillshadeShadowColor(PropertyValue<Color> value) {
+ if (value == getHillshadeShadowColor())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeShadowColor>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HillshadeLayer::setHillshadeShadowColorTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeShadowColor>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HillshadeLayer::getHillshadeShadowColorTransition() const {
+ return impl().paint.template get<HillshadeShadowColor>().options;
+}
+
+PropertyValue<Color> HillshadeLayer::getDefaultHillshadeHighlightColor() {
+ return { Color::white() };
+}
+
+PropertyValue<Color> HillshadeLayer::getHillshadeHighlightColor() const {
+ return impl().paint.template get<HillshadeHighlightColor>().value;
+}
+
+void HillshadeLayer::setHillshadeHighlightColor(PropertyValue<Color> value) {
+ if (value == getHillshadeHighlightColor())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeHighlightColor>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HillshadeLayer::setHillshadeHighlightColorTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeHighlightColor>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HillshadeLayer::getHillshadeHighlightColorTransition() const {
+ return impl().paint.template get<HillshadeHighlightColor>().options;
+}
+
+PropertyValue<Color> HillshadeLayer::getDefaultHillshadeAccentColor() {
+ return { Color::black() };
+}
+
+PropertyValue<Color> HillshadeLayer::getHillshadeAccentColor() const {
+ return impl().paint.template get<HillshadeAccentColor>().value;
+}
+
+void HillshadeLayer::setHillshadeAccentColor(PropertyValue<Color> value) {
+ if (value == getHillshadeAccentColor())
+ return;
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeAccentColor>().value = value;
+ baseImpl = std::move(impl_);
+ observer->onLayerChanged(*this);
+}
+
+void HillshadeLayer::setHillshadeAccentColorTransition(const TransitionOptions& options) {
+ auto impl_ = mutableImpl();
+ impl_->paint.template get<HillshadeAccentColor>().options = options;
+ baseImpl = std::move(impl_);
+}
+
+TransitionOptions HillshadeLayer::getHillshadeAccentColorTransition() const {
+ return impl().paint.template get<HillshadeAccentColor>().options;
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/hillshade_layer_impl.cpp b/src/mbgl/style/layers/hillshade_layer_impl.cpp
new file mode 100644
index 0000000000..ed5aa922bf
--- /dev/null
+++ b/src/mbgl/style/layers/hillshade_layer_impl.cpp
@@ -0,0 +1,11 @@
+#include <mbgl/style/layers/hillshade_layer_impl.hpp>
+
+namespace mbgl {
+namespace style {
+
+bool HillshadeLayer::Impl::hasLayoutDifference(const Layer::Impl&) const {
+ return false;
+}
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/hillshade_layer_impl.hpp b/src/mbgl/style/layers/hillshade_layer_impl.hpp
new file mode 100644
index 0000000000..5618b7dfe2
--- /dev/null
+++ b/src/mbgl/style/layers/hillshade_layer_impl.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <mbgl/style/layer_impl.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer_properties.hpp>
+
+namespace mbgl {
+namespace style {
+
+class HillshadeLayer::Impl : public Layer::Impl {
+public:
+ using Layer::Impl::Impl;
+
+ bool hasLayoutDifference(const Layer::Impl&) const override;
+ void stringifyLayout(rapidjson::Writer<rapidjson::StringBuffer>&) const override;
+
+ HillshadePaintProperties::Transitionable paint;
+};
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/hillshade_layer_properties.cpp b/src/mbgl/style/layers/hillshade_layer_properties.cpp
new file mode 100644
index 0000000000..f296ab4520
--- /dev/null
+++ b/src/mbgl/style/layers/hillshade_layer_properties.cpp
@@ -0,0 +1,9 @@
+// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
+
+#include <mbgl/style/layers/hillshade_layer_properties.hpp>
+
+namespace mbgl {
+namespace style {
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/hillshade_layer_properties.hpp b/src/mbgl/style/layers/hillshade_layer_properties.hpp
new file mode 100644
index 0000000000..260d7ea808
--- /dev/null
+++ b/src/mbgl/style/layers/hillshade_layer_properties.hpp
@@ -0,0 +1,49 @@
+// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`.
+
+#pragma once
+
+#include <mbgl/style/types.hpp>
+#include <mbgl/style/layout_property.hpp>
+#include <mbgl/style/paint_property.hpp>
+#include <mbgl/style/properties.hpp>
+#include <mbgl/programs/attributes.hpp>
+#include <mbgl/programs/uniforms.hpp>
+
+namespace mbgl {
+namespace style {
+
+struct HillshadeIlluminationDirection : PaintProperty<float> {
+ static float defaultValue() { return 335; }
+};
+
+struct HillshadeIlluminationAnchor : PaintProperty<HillshadeIlluminationAnchorType> {
+ static HillshadeIlluminationAnchorType defaultValue() { return HillshadeIlluminationAnchorType::Viewport; }
+};
+
+struct HillshadeExaggeration : PaintProperty<float> {
+ static float defaultValue() { return 0.5; }
+};
+
+struct HillshadeShadowColor : PaintProperty<Color> {
+ static Color defaultValue() { return Color::black(); }
+};
+
+struct HillshadeHighlightColor : PaintProperty<Color> {
+ static Color defaultValue() { return Color::white(); }
+};
+
+struct HillshadeAccentColor : PaintProperty<Color> {
+ static Color defaultValue() { return Color::black(); }
+};
+
+class HillshadePaintProperties : public Properties<
+ HillshadeIlluminationDirection,
+ HillshadeIlluminationAnchor,
+ HillshadeExaggeration,
+ HillshadeShadowColor,
+ HillshadeHighlightColor,
+ HillshadeAccentColor
+> {};
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs
index 573aabda8b..be44bb353d 100644
--- a/src/mbgl/style/layers/layer.cpp.ejs
+++ b/src/mbgl/style/layers/layer.cpp.ejs
@@ -59,7 +59,7 @@ const std::string& <%- camelize(type) %>Layer::getSourceID() const {
return impl().source;
}
-<% if (type !== 'raster') { -%>
+<% if (type !== 'raster' && type !== 'hillshade') { -%>
void <%- camelize(type) %>Layer::setSourceLayer(const std::string& sourceLayer) {
auto impl_ = mutableImpl();
impl_->sourceLayer = sourceLayer;
diff --git a/src/mbgl/style/sources/raster_dem_source.cpp b/src/mbgl/style/sources/raster_dem_source.cpp
new file mode 100644
index 0000000000..dc9feb8eeb
--- /dev/null
+++ b/src/mbgl/style/sources/raster_dem_source.cpp
@@ -0,0 +1,19 @@
+#include <mbgl/style/sources/raster_dem_source.hpp>
+#include <mbgl/style/sources/raster_source_impl.hpp>
+#include <mbgl/style/source_observer.hpp>
+#include <mbgl/style/conversion/json.hpp>
+#include <mbgl/style/conversion/tileset.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/util/mapbox.hpp>
+
+namespace mbgl {
+namespace style {
+
+RasterDEMSource::RasterDEMSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize)
+ : RasterSource(std::move(id), urlOrTileset_, tileSize, SourceType::RasterDEM){
+}
+
+
+
+} // namespace style
+} // namespace mbgl
diff --git a/src/mbgl/style/sources/raster_source.cpp b/src/mbgl/style/sources/raster_source.cpp
index 0a0412a4ed..53f29d660b 100644
--- a/src/mbgl/style/sources/raster_source.cpp
+++ b/src/mbgl/style/sources/raster_source.cpp
@@ -9,8 +9,8 @@
namespace mbgl {
namespace style {
-RasterSource::RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize)
- : Source(makeMutable<Impl>(std::move(id), tileSize)),
+RasterSource::RasterSource(std::string id, variant<std::string, Tileset> urlOrTileset_, uint16_t tileSize, SourceType sourceType)
+ : Source(makeMutable<Impl>(sourceType, std::move(id), tileSize)),
urlOrTileset(std::move(urlOrTileset_)) {
}
diff --git a/src/mbgl/style/sources/raster_source_impl.cpp b/src/mbgl/style/sources/raster_source_impl.cpp
index 50dae1f07e..4db25aafd1 100644
--- a/src/mbgl/style/sources/raster_source_impl.cpp
+++ b/src/mbgl/style/sources/raster_source_impl.cpp
@@ -3,8 +3,8 @@
namespace mbgl {
namespace style {
-RasterSource::Impl::Impl(std::string id_, uint16_t tileSize_)
- : Source::Impl(SourceType::Raster, std::move(id_)),
+RasterSource::Impl::Impl(SourceType sourceType, std::string id_, uint16_t tileSize_)
+ : Source::Impl(sourceType, std::move(id_)),
tileSize(tileSize_) {
}
diff --git a/src/mbgl/style/sources/raster_source_impl.hpp b/src/mbgl/style/sources/raster_source_impl.hpp
index c41d5485b2..96f59a2159 100644
--- a/src/mbgl/style/sources/raster_source_impl.hpp
+++ b/src/mbgl/style/sources/raster_source_impl.hpp
@@ -8,7 +8,7 @@ namespace style {
class RasterSource::Impl : public Source::Impl {
public:
- Impl(std::string id, uint16_t tileSize);
+ Impl(SourceType sourceType, std::string id, uint16_t tileSize);
Impl(const Impl&, Tileset);
optional<Tileset> getTileset() const;
diff --git a/src/mbgl/style/style_impl.cpp b/src/mbgl/style/style_impl.cpp
index 3214c6316e..f2a9c0f222 100644
--- a/src/mbgl/style/style_impl.cpp
+++ b/src/mbgl/style/style_impl.cpp
@@ -9,6 +9,7 @@
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/style/layers/circle_layer.hpp>
#include <mbgl/style/layers/raster_layer.hpp>
+#include <mbgl/style/layers/hillshade_layer.hpp>
#include <mbgl/style/layer_impl.hpp>
#include <mbgl/style/parser.hpp>
#include <mbgl/style/transition_options.hpp>
diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp
index cd5e597fc0..bdfa20a047 100644
--- a/src/mbgl/style/types.cpp
+++ b/src/mbgl/style/types.cpp
@@ -25,6 +25,11 @@ MBGL_DEFINE_ENUM(TranslateAnchorType, {
{ TranslateAnchorType::Viewport, "viewport" },
});
+MBGL_DEFINE_ENUM(HillshadeIlluminationAnchorType, {
+ { HillshadeIlluminationAnchorType::Map, "map" },
+ { HillshadeIlluminationAnchorType::Viewport, "viewport" },
+});
+
MBGL_DEFINE_ENUM(RotateAnchorType, {
{ RotateAnchorType::Map, "map" },
{ RotateAnchorType::Viewport, "viewport" },
diff --git a/src/mbgl/tile/raster_dem_tile.cpp b/src/mbgl/tile/raster_dem_tile.cpp
new file mode 100644
index 0000000000..b270378ece
--- /dev/null
+++ b/src/mbgl/tile/raster_dem_tile.cpp
@@ -0,0 +1,124 @@
+#include <mbgl/tile/raster_dem_tile.hpp>
+#include <mbgl/tile/raster_dem_tile_worker.hpp>
+#include <mbgl/tile/tile_observer.hpp>
+#include <mbgl/tile/tile_loader_impl.hpp>
+#include <mbgl/style/source.hpp>
+#include <mbgl/storage/resource.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/storage/file_source.hpp>
+#include <mbgl/renderer/tile_parameters.hpp>
+#include <mbgl/renderer/buckets/hillshade_bucket.hpp>
+#include <mbgl/actor/scheduler.hpp>
+
+namespace mbgl {
+
+RasterDEMTile::RasterDEMTile(const OverscaledTileID& id_,
+ const TileParameters& parameters,
+ const Tileset& tileset)
+ : Tile(id_),
+ loader(*this, id_, parameters, tileset),
+ mailbox(std::make_shared<Mailbox>(*Scheduler::GetCurrent())),
+ worker(parameters.workerScheduler,
+ ActorRef<RasterDEMTile>(*this, mailbox)) {
+
+ if ( id.canonical.y == 0 ){
+ // this tile doesn't have upper neighboring tiles so marked those as backfilled
+ neighboringTiles = neighboringTiles | DEMTileNeighbors::NoUpper;
+ }
+
+ if (id.canonical.y + 1 == std::pow(2, id.canonical.z)){
+ // this tile doesn't have lower neighboring tiles so marked those as backfilled
+ neighboringTiles = neighboringTiles | DEMTileNeighbors::NoLower;
+ }
+}
+
+RasterDEMTile::~RasterDEMTile() = default;
+
+void RasterDEMTile::setError(std::exception_ptr err) {
+ loaded = true;
+ observer->onTileError(*this, err);
+}
+
+void RasterDEMTile::setMetadata(optional<Timestamp> modified_, optional<Timestamp> expires_) {
+ modified = modified_;
+ expires = expires_;
+}
+
+void RasterDEMTile::setData(std::shared_ptr<const std::string> data) {
+ pending = true;
+ ++correlationID;
+ worker.invoke(&RasterDEMTileWorker::parse, data, correlationID);
+}
+
+void RasterDEMTile::onParsed(std::unique_ptr<HillshadeBucket> result, const uint64_t resultCorrelationID) {
+ bucket = std::move(result);
+ loaded = true;
+ if (resultCorrelationID == correlationID) {
+ pending = false;
+ }
+ renderable = bucket ? true : false;
+ observer->onTileChanged(*this);
+}
+
+void RasterDEMTile::onError(std::exception_ptr err, const uint64_t resultCorrelationID) {
+ loaded = true;
+ if (resultCorrelationID == correlationID) {
+ pending = false;
+ }
+ observer->onTileError(*this, err);
+}
+
+void RasterDEMTile::upload(gl::Context& context) {
+ if (bucket) {
+ bucket->upload(context);
+ }
+}
+
+
+Bucket* RasterDEMTile::getBucket(const style::Layer::Impl&) const {
+ return bucket.get();
+}
+
+HillshadeBucket* RasterDEMTile::getBucket() const {
+ return bucket.get();
+}
+
+void RasterDEMTile::backfillBorder(const RasterDEMTile& borderTile, const DEMTileNeighbors mask) {
+ int32_t dx = borderTile.id.canonical.x - id.canonical.x;
+ const int8_t dy = borderTile.id.canonical.y - id.canonical.y;
+ const uint32_t dim = pow(2, id.canonical.z);
+ if (dx == 0 && dy == 0) return;
+ if (std::abs(dy) > 1) return;
+ // neighbor is in another world wrap
+ if (std::abs(dx) > 1) {
+ if (std::abs(int(dx + dim)) == 1) {
+ dx += dim;
+ } else if (std::abs(int(dx - dim)) == 1) {
+ dx -= dim;
+ }
+ }
+ const HillshadeBucket* borderBucket = borderTile.getBucket();
+ if (borderBucket) {
+ const DEMData& borderDEM = borderBucket->getDEMData();
+ DEMData& tileDEM = bucket->getDEMData();
+
+ tileDEM.backfillBorder(borderDEM, dx, dy);
+ // update the bitmask to indicate that this tiles have been backfilled by flipping the relevant bit
+ this->neighboringTiles = this->neighboringTiles | mask;
+ // mark HillshadeBucket.prepared as false so it runs through the prepare render pass
+ // with the new texture data we just backfilled
+ bucket->setPrepared(false);
+ }
+}
+
+void RasterDEMTile::setMask(TileMask&& mask) {
+ if (bucket) {
+ bucket->setMask(std::move(mask));
+ }
+}
+
+void RasterDEMTile::setNecessity(TileNecessity necessity) {
+ loader.setNecessity(necessity);
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/tile/raster_dem_tile.hpp b/src/mbgl/tile/raster_dem_tile.hpp
new file mode 100644
index 0000000000..68f8a91e00
--- /dev/null
+++ b/src/mbgl/tile/raster_dem_tile.hpp
@@ -0,0 +1,104 @@
+#pragma once
+
+#include <mbgl/tile/tile.hpp>
+#include <mbgl/tile/tile_loader.hpp>
+#include <mbgl/tile/raster_dem_tile_worker.hpp>
+#include <mbgl/actor/actor.hpp>
+
+namespace mbgl {
+
+class Tileset;
+class TileParameters;
+class HillshadeBucket;
+
+enum class DEMTileNeighbors : uint8_t {
+ // 0b00000000
+ Empty = 0 << 1,
+
+ // 0b00000001
+ Left = 1 << 0,
+ // 0b00000010
+ Right = 1 << 1,
+ // 0b00000100
+ TopLeft = 1 << 2,
+ // 0b00001000
+ TopCenter = 1 << 3,
+ // 0b00010000
+ TopRight = 1 << 4,
+ // 0b00100000
+ BottomLeft = 1 << 5,
+ // 0b01000000
+ BottomCenter = 1 << 6,
+ // 0b10000000
+ BottomRight = 1 << 7,
+
+ // helper enums for tiles with no upper/lower neighbors
+ // and completely backfilled tiles
+
+ // 0b00011100
+ NoUpper = 0b00011100,
+ // 0b11100000
+ NoLower = 0b11100000,
+ // 0b11111111
+ Complete = 0b11111111
+};
+
+inline DEMTileNeighbors operator|(DEMTileNeighbors a, DEMTileNeighbors b) {
+ return static_cast<DEMTileNeighbors>(int(a) | int(b));
+};
+
+inline DEMTileNeighbors operator&(DEMTileNeighbors a, DEMTileNeighbors b) {
+ return static_cast<DEMTileNeighbors>(int(a) & int(b));
+}
+
+inline bool operator!=(DEMTileNeighbors a, DEMTileNeighbors b) {
+ return static_cast<unsigned char>(a) != static_cast<unsigned char>(b);
+}
+
+namespace style {
+class Layer;
+} // namespace style
+
+class RasterDEMTile : public Tile {
+public:
+ RasterDEMTile(const OverscaledTileID&,
+ const TileParameters&,
+ const Tileset&);
+ ~RasterDEMTile() override;
+
+ void setNecessity(TileNecessity) final;
+
+ void setError(std::exception_ptr);
+ void setMetadata(optional<Timestamp> modified, optional<Timestamp> expires);
+ void setData(std::shared_ptr<const std::string> data);
+
+ void upload(gl::Context&) override;
+ Bucket* getBucket(const style::Layer::Impl&) const override;
+
+ HillshadeBucket* getBucket() const;
+ void backfillBorder(const RasterDEMTile& borderTile, const DEMTileNeighbors mask);
+
+ // neighboringTiles is a bitmask for which neighboring tiles have been backfilled
+ // there are max 8 possible neighboring tiles, so each bit represents one neighbor
+ DEMTileNeighbors neighboringTiles = DEMTileNeighbors::Empty;
+
+ void setMask(TileMask&&) override;
+
+ void onParsed(std::unique_ptr<HillshadeBucket> result, uint64_t correlationID);
+ void onError(std::exception_ptr, uint64_t correlationID);
+
+private:
+ TileLoader<RasterDEMTile> loader;
+
+ std::shared_ptr<Mailbox> mailbox;
+ Actor<RasterDEMTileWorker> worker;
+
+ uint64_t correlationID = 0;
+
+ // Contains the Bucket object for the tile. Buckets are render
+ // objects and they get added by tile parsing operations.
+ std::unique_ptr<HillshadeBucket> bucket;
+
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/tile/raster_dem_tile_worker.cpp b/src/mbgl/tile/raster_dem_tile_worker.cpp
new file mode 100644
index 0000000000..ed8573788f
--- /dev/null
+++ b/src/mbgl/tile/raster_dem_tile_worker.cpp
@@ -0,0 +1,27 @@
+#include <mbgl/tile/raster_dem_tile_worker.hpp>
+#include <mbgl/tile/raster_dem_tile.hpp>
+#include <mbgl/renderer/buckets/hillshade_bucket.hpp>
+#include <mbgl/actor/actor.hpp>
+#include <mbgl/util/premultiply.hpp>
+
+namespace mbgl {
+
+RasterDEMTileWorker::RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef<RasterDEMTile> parent_)
+ : parent(std::move(parent_)) {
+}
+
+void RasterDEMTileWorker::parse(std::shared_ptr<const std::string> data, uint64_t correlationID) {
+ if (!data) {
+ parent.invoke(&RasterDEMTile::onParsed, nullptr, correlationID); // No data; empty tile.
+ return;
+ }
+
+ try {
+ auto bucket = std::make_unique<HillshadeBucket>(decodeImage(*data));
+ parent.invoke(&RasterDEMTile::onParsed, std::move(bucket), correlationID);
+ } catch (...) {
+ parent.invoke(&RasterDEMTile::onError, std::current_exception(), correlationID);
+ }
+}
+
+} // namespace mbgl
diff --git a/src/mbgl/tile/raster_dem_tile_worker.hpp b/src/mbgl/tile/raster_dem_tile_worker.hpp
new file mode 100644
index 0000000000..14fd1f43b5
--- /dev/null
+++ b/src/mbgl/tile/raster_dem_tile_worker.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <mbgl/actor/actor_ref.hpp>
+
+#include <memory>
+#include <string>
+
+namespace mbgl {
+
+class RasterDEMTile;
+
+class RasterDEMTileWorker {
+public:
+ RasterDEMTileWorker(ActorRef<RasterDEMTileWorker>, ActorRef<RasterDEMTile>);
+
+ void parse(std::shared_ptr<const std::string> data, uint64_t correlationID);
+
+private:
+ ActorRef<RasterDEMTile> parent;
+};
+
+} // namespace mbgl
diff --git a/src/mbgl/tile/raster_tile.cpp b/src/mbgl/tile/raster_tile.cpp
index 85fcea77b7..ff23d4493e 100644
--- a/src/mbgl/tile/raster_tile.cpp
+++ b/src/mbgl/tile/raster_tile.cpp
@@ -24,9 +24,6 @@ RasterTile::RasterTile(const OverscaledTileID& id_,
RasterTile::~RasterTile() = default;
-void RasterTile::cancel() {
-}
-
void RasterTile::setError(std::exception_ptr err) {
loaded = true;
observer->onTileError(*this, err);
diff --git a/src/mbgl/tile/raster_tile.hpp b/src/mbgl/tile/raster_tile.hpp
index 192769ed8f..e25329119a 100644
--- a/src/mbgl/tile/raster_tile.hpp
+++ b/src/mbgl/tile/raster_tile.hpp
@@ -20,7 +20,7 @@ public:
RasterTile(const OverscaledTileID&,
const TileParameters&,
const Tileset&);
- ~RasterTile() final;
+ ~RasterTile() override;
void setNecessity(TileNecessity) final;
@@ -28,8 +28,6 @@ public:
void setMetadata(optional<Timestamp> modified, optional<Timestamp> expires);
void setData(std::shared_ptr<const std::string> data);
- void cancel() override;
-
void upload(gl::Context&) override;
Bucket* getBucket(const style::Layer::Impl&) const override;
diff --git a/src/mbgl/tile/tile.cpp b/src/mbgl/tile/tile.cpp
index 85899a98cb..88db2ba07c 100644
--- a/src/mbgl/tile/tile.cpp
+++ b/src/mbgl/tile/tile.cpp
@@ -18,6 +18,9 @@ void Tile::setObserver(TileObserver* observer_) {
observer = observer_;
}
+void Tile::cancel() {
+}
+
void Tile::setTriedCache() {
triedOptional = true;
observer->onTileChanged(*this);
diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp
index 1bed180f3d..23365c6ae3 100644
--- a/src/mbgl/tile/tile.hpp
+++ b/src/mbgl/tile/tile.hpp
@@ -43,7 +43,7 @@ public:
virtual void setNecessity(TileNecessity) {}
// Mark this tile as no longer needed and cancel any pending work.
- virtual void cancel() = 0;
+ virtual void cancel();
virtual void upload(gl::Context&) = 0;
virtual Bucket* getBucket(const style::Layer::Impl&) const = 0;
diff --git a/src/mbgl/tile/tile_cache.cpp b/src/mbgl/tile/tile_cache.cpp
index 3fafb1259c..463d397608 100644
--- a/src/mbgl/tile/tile_cache.cpp
+++ b/src/mbgl/tile/tile_cache.cpp
@@ -33,13 +33,22 @@ void TileCache::add(const OverscaledTileID& key, std::unique_ptr<Tile> tile) {
// purge oldest key/tile if necessary
if (orderedKeys.size() > size) {
- get(orderedKeys.front());
+ pop(orderedKeys.front());
}
assert(orderedKeys.size() <= size);
}
-std::unique_ptr<Tile> TileCache::get(const OverscaledTileID& key) {
+Tile* TileCache::get(const OverscaledTileID& key) {
+ auto it = tiles.find(key);
+ if (it != tiles.end()) {
+ return it->second.get();
+ } else {
+ return nullptr;
+ }
+}
+
+std::unique_ptr<Tile> TileCache::pop(const OverscaledTileID& key) {
std::unique_ptr<Tile> tile;
diff --git a/src/mbgl/tile/tile_cache.hpp b/src/mbgl/tile/tile_cache.hpp
index 80fe98a20c..88358b8cdc 100644
--- a/src/mbgl/tile/tile_cache.hpp
+++ b/src/mbgl/tile/tile_cache.hpp
@@ -17,7 +17,8 @@ public:
void setSize(size_t);
size_t getSize() const { return size; };
void add(const OverscaledTileID& key, std::unique_ptr<Tile> data);
- std::unique_ptr<Tile> get(const OverscaledTileID& key);
+ std::unique_ptr<Tile> pop(const OverscaledTileID& key);
+ Tile* get(const OverscaledTileID& key);
bool has(const OverscaledTileID& key);
void clear();
diff --git a/src/mbgl/util/mapbox.cpp b/src/mbgl/util/mapbox.cpp
index 802b527a26..cdd51a293d 100644
--- a/src/mbgl/util/mapbox.cpp
+++ b/src/mbgl/util/mapbox.cpp
@@ -133,7 +133,7 @@ canonicalizeTileURL(const std::string& str, const style::SourceType type, const
std::string result = "mapbox://tiles/";
result.append(str, path.directory.first + versionLen, path.directory.second - versionLen);
result.append(str, path.filename.first, path.filename.second);
- if (type == style::SourceType::Raster) {
+ if (type == style::SourceType::Raster || type == style::SourceType::RasterDEM) {
result += tileSize == util::tileSize ? "@2x" : "{ratio}";
}
diff --git a/test/geometry/dem_data.test.cpp b/test/geometry/dem_data.test.cpp
new file mode 100644
index 0000000000..30091973b2
--- /dev/null
+++ b/test/geometry/dem_data.test.cpp
@@ -0,0 +1,140 @@
+#include <mbgl/test/util.hpp>
+
+#include <mbgl/util/image.hpp>
+#include <mbgl/geometry/dem_data.hpp>
+
+using namespace mbgl;
+
+auto fakeImage = [](Size s) {
+ PremultipliedImage img = PremultipliedImage(s);
+
+ for (size_t i = 0; i < img.bytes(); i ++) {
+ img.data[i] = (i+1) % 4 == 0 ? 1 : std::rand() % 255;
+ }
+ return img;
+};
+
+TEST(DEMData, Constructor) {
+ PremultipliedImage image = fakeImage({16, 16});
+ DEMData pyramid(image);
+
+ EXPECT_EQ(pyramid.dim, 16);
+ EXPECT_EQ(pyramid.border, 8);
+ EXPECT_EQ(pyramid.stride, 32);
+ EXPECT_EQ(pyramid.getImage()->bytes(), size_t(32*32*4));
+ EXPECT_EQ(pyramid.dim, 16);
+ EXPECT_EQ(pyramid.border, 8);
+};
+
+TEST(DEMData, RoundTrip) {
+ PremultipliedImage image = fakeImage({16, 16});
+ DEMData pyramid(image);
+
+ pyramid.set(4, 6, 255);
+ EXPECT_EQ(pyramid.get(4, 6), 255);
+}
+
+TEST(DEMData, InitialBackfill) {
+
+ PremultipliedImage image1 = fakeImage({4, 4});
+ DEMData dem1(image1);
+
+ bool nonempty = true;
+ // checking that a 1 px border around the fake image has been populated
+ // with a non-empty pixel value
+ for (int x = -1; x < 5; x++){
+ for (int y = -1; y < 5; y ++) {
+ if (dem1.get(x, y) == -65536) {
+ nonempty = false;
+ break;
+ }
+ }
+ }
+ EXPECT_TRUE(nonempty);
+
+ bool verticalBorderMatch = true;
+ int vertx[] = {-1, 4};
+ for (int x : vertx) {
+ for (int y = 0; y < 4; y++) {
+ if (dem1.get(x, y) != dem1.get(x < 0 ? x + 1 : x - 1, y)) {
+ verticalBorderMatch = false;
+ break;
+ }
+ }
+ }
+ // vertical border of DEM data is initially equal to next column of data
+ EXPECT_TRUE(verticalBorderMatch);
+
+ // horizontal borders empty
+ bool horizontalBorderMatch = true;
+ int horiz[] = {-1, 4};
+ for (int y : horiz) {
+ for (int x = 0; x < 4; x++) {
+ if (dem1.get(x, y) != dem1.get(x, y < 0 ? y + 1 : y - 1)) {
+ horizontalBorderMatch = false;
+ break;
+ }
+ }
+ }
+ //horizontal border of DEM data is initially equal to next row of data
+
+ EXPECT_TRUE(horizontalBorderMatch);
+ // -1, 1 corner initially equal to closest corner data
+ EXPECT_TRUE(dem1.get(-1, 4) == dem1.get(0, 3));
+ // 1, 1 corner initially equal to closest corner data
+ EXPECT_TRUE(dem1.get(4, 4) == dem1.get(3, 3));
+ // -1, -1 corner initially equal to closest corner data
+ EXPECT_TRUE(dem1.get(-1, -1) == dem1.get(0, 0));
+ // -1, 1 corner initially equal to closest corner data
+ EXPECT_TRUE(dem1.get(4, -1) == dem1.get(3, 0));
+};
+
+TEST(DEMData, BackfillNeighbor) {
+ PremultipliedImage image1 = fakeImage({4, 4});
+ DEMData dem0(image1);
+
+ PremultipliedImage image2 = fakeImage({4, 4});
+ DEMData dem1(image2);
+
+ dem0.backfillBorder(dem1, -1, 0);
+ for (int y = 0; y < 4; y++) {
+ // dx = -1, dy = 0, so the left edge of dem1 should equal the right edge of dem0
+ // backfills Left neighbor
+ EXPECT_TRUE(dem0.get(-1, y) == dem1.get(3, y));
+
+ }
+
+ dem0.backfillBorder(dem1, 0, -1);
+ // backfills TopCenter neighbor
+ for (int x = 0; x < 4; x++) {
+ EXPECT_TRUE(dem0.get(x, -1) == dem1.get(x, 3));
+ }
+
+ dem0.backfillBorder(dem1, 1, 0);
+ // backfills Right neighbor
+ for (int y = 0; y < 4; y++) {
+ EXPECT_TRUE(dem0.get(4, y) == dem1.get(0, y));
+ }
+
+ dem0.backfillBorder(dem1, 0, 1);
+ // backfills BottomCenter neighbor
+ for (int x = 0; x < 4; x++) {
+ EXPECT_TRUE(dem0.get(x, 4) == dem1.get(x, 0));
+ }
+
+ dem0.backfillBorder(dem1, -1, 1);
+ // backfulls TopRight neighbor
+ EXPECT_TRUE(dem0.get(-1, 4) == dem1.get(3, 0));
+
+ dem0.backfillBorder(dem1, 1, 1);
+ // backfulls BottomRight neighbor
+ EXPECT_TRUE(dem0.get(4, 4) == dem1.get(0, 0));
+
+ dem0.backfillBorder(dem1, -1, -1);
+ // backfulls TopLeft neighbor
+ EXPECT_TRUE(dem0.get(-1, -1) == dem1.get(3, 3));
+
+ dem0.backfillBorder(dem1, 1, -1);
+ // backfulls BottomLeft neighbor
+ EXPECT_TRUE(dem0.get(4, -1) == dem1.get(0, 3));
+};
diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp
index eb419e8080..6cb01146f6 100644
--- a/test/style/source.test.cpp
+++ b/test/style/source.test.cpp
@@ -6,14 +6,17 @@
#include <mbgl/style/style.hpp>
#include <mbgl/style/source_impl.hpp>
#include <mbgl/style/sources/raster_source.hpp>
+#include <mbgl/style/sources/raster_dem_source.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/style/sources/custom_geometry_source.hpp>
+#include <mbgl/style/layers/hillshade_layer.cpp>
#include <mbgl/style/layers/raster_layer.cpp>
#include <mbgl/style/layers/line_layer.hpp>
#include <mbgl/renderer/sources/render_raster_source.hpp>
+#include <mbgl/renderer/sources/render_raster_dem_source.hpp>
#include <mbgl/renderer/sources/render_vector_source.hpp>
#include <mbgl/renderer/sources/render_geojson_source.hpp>
#include <mbgl/renderer/tile_parameters.hpp>
@@ -173,6 +176,44 @@ TEST(Source, RasterTileEmpty) {
test.run();
}
+TEST(Source, RasterDEMTileEmpty) {
+ SourceTest test;
+
+ test.fileSource.tileResponse = [&] (const Resource&) {
+ Response response;
+ response.noContent = true;
+ return response;
+ };
+
+ HillshadeLayer layer("id", "source");
+ std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }};
+
+ Tileset tileset;
+ tileset.tiles = { "tiles" };
+
+ RasterDEMSource source("source", tileset, 512);
+ source.loadDescription(test.fileSource);
+
+ test.renderSourceObserver.tileChanged = [&] (RenderSource& source_, const OverscaledTileID&) {
+ EXPECT_EQ("source", source_.baseImpl->id);
+ test.end();
+ };
+
+ test.renderSourceObserver.tileError = [&] (RenderSource&, const OverscaledTileID&, std::exception_ptr) {
+ FAIL() << "Should never be called";
+ };
+
+ auto renderSource = RenderSource::create(source.baseImpl);
+ renderSource->setObserver(&test.renderSourceObserver);
+ renderSource->update(source.baseImpl,
+ layers,
+ true,
+ true,
+ test.tileParameters);
+
+ test.run();
+}
+
TEST(Source, VectorTileEmpty) {
SourceTest test;
@@ -251,6 +292,44 @@ TEST(Source, RasterTileFail) {
test.run();
}
+TEST(Source, RasterDEMTileFail) {
+ SourceTest test;
+
+ test.fileSource.tileResponse = [&] (const Resource&) {
+ Response response;
+ response.error = std::make_unique<Response::Error>(
+ Response::Error::Reason::Other,
+ "Failed by the test case");
+ return response;
+ };
+
+ HillshadeLayer layer("id", "source");
+ std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }};
+
+ Tileset tileset;
+ tileset.tiles = { "tiles" };
+
+ RasterDEMSource source("source", tileset, 512);
+ source.loadDescription(test.fileSource);
+
+ test.renderSourceObserver.tileError = [&] (RenderSource& source_, const OverscaledTileID& tileID, std::exception_ptr error) {
+ EXPECT_EQ(SourceType::RasterDEM, source_.baseImpl->type);
+ EXPECT_EQ(OverscaledTileID(0, 0, 0), tileID);
+ EXPECT_EQ("Failed by the test case", util::toString(error));
+ test.end();
+ };
+
+ auto renderSource = RenderSource::create(source.baseImpl);
+ renderSource->setObserver(&test.renderSourceObserver);
+ renderSource->update(source.baseImpl,
+ layers,
+ true,
+ true,
+ test.tileParameters);
+
+ test.run();
+}
+
TEST(Source, VectorTileFail) {
SourceTest test;
@@ -328,6 +407,43 @@ TEST(Source, RasterTileCorrupt) {
test.run();
}
+TEST(Source, RasterDEMTileCorrupt) {
+ SourceTest test;
+
+ test.fileSource.tileResponse = [&] (const Resource&) {
+ Response response;
+ response.data = std::make_unique<std::string>("CORRUPTED");
+ return response;
+ };
+
+ HillshadeLayer layer("id", "source");
+ std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }};
+
+ Tileset tileset;
+ tileset.tiles = { "tiles" };
+
+ RasterDEMSource source("source", tileset, 512);
+ source.loadDescription(test.fileSource);
+
+ test.renderSourceObserver.tileError = [&] (RenderSource& source_, const OverscaledTileID& tileID, std::exception_ptr error) {
+ EXPECT_EQ(source_.baseImpl->type, SourceType::RasterDEM);
+ EXPECT_EQ(OverscaledTileID(0, 0, 0), tileID);
+ EXPECT_TRUE(bool(error));
+ // Not asserting on platform-specific error text.
+ test.end();
+ };
+
+ auto renderSource = RenderSource::create(source.baseImpl);
+ renderSource->setObserver(&test.renderSourceObserver);
+ renderSource->update(source.baseImpl,
+ layers,
+ true,
+ true,
+ test.tileParameters);
+
+ test.run();
+}
+
TEST(Source, VectorTileCorrupt) {
SourceTest test;
@@ -402,6 +518,42 @@ TEST(Source, RasterTileCancel) {
test.run();
}
+TEST(Source, RasterDEMTileCancel) {
+ SourceTest test;
+
+ test.fileSource.tileResponse = [&] (const Resource&) {
+ test.end();
+ return optional<Response>();
+ };
+
+ HillshadeLayer layer("id", "source");
+ std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }};
+
+ Tileset tileset;
+ tileset.tiles = { "tiles" };
+
+ RasterDEMSource source("source", tileset, 512);
+ source.loadDescription(test.fileSource);
+
+ test.renderSourceObserver.tileChanged = [&] (RenderSource&, const OverscaledTileID&) {
+ FAIL() << "Should never be called";
+ };
+
+ test.renderSourceObserver.tileError = [&] (RenderSource&, const OverscaledTileID&, std::exception_ptr) {
+ FAIL() << "Should never be called";
+ };
+
+ auto renderSource = RenderSource::create(source.baseImpl);
+ renderSource->setObserver(&test.renderSourceObserver);
+ renderSource->update(source.baseImpl,
+ layers,
+ true,
+ true,
+ test.tileParameters);
+
+ test.run();
+}
+
TEST(Source, VectorTileCancel) {
SourceTest test;
@@ -484,6 +636,48 @@ TEST(Source, RasterTileAttribution) {
test.run();
}
+TEST(Source, RasterDEMTileAttribution) {
+ SourceTest test;
+
+ HillshadeLayer layer("id", "source");
+ std::vector<Immutable<Layer::Impl>> layers {{ layer.baseImpl }};
+
+ std::string mapbox = ("<a href='https://www.mapbox.com/about/maps/' target='_blank'>&copy; Mapbox</a> ");
+
+ test.fileSource.tileResponse = [&] (const Resource&) {
+ Response response;
+ response.noContent = true;
+ return response;
+ };
+
+ test.fileSource.sourceResponse = [&] (const Resource& resource) {
+ EXPECT_EQ("url", resource.url);
+ Response response;
+ response.data = std::make_unique<std::string>(R"TILEJSON({ "tilejson": "2.1.0", "attribution": ")TILEJSON" +
+ mapbox +
+ R"TILEJSON(", "tiles": [ "tiles" ] })TILEJSON");
+ return response;
+ };
+
+ test.styleObserver.sourceChanged = [&] (Source& source) {
+ EXPECT_EQ(mapbox, source.getAttribution());
+ test.end();
+ };
+
+ RasterDEMSource source("source", "url", 512);
+ source.setObserver(&test.styleObserver);
+ source.loadDescription(test.fileSource);
+
+ auto renderSource = RenderSource::create(source.baseImpl);
+ renderSource->update(source.baseImpl,
+ layers,
+ true,
+ true,
+ test.tileParameters);
+
+ test.run();
+}
+
TEST(Source, GeoJSonSourceUrlUpdate) {
SourceTest test;
diff --git a/test/tile/raster_dem_tile.test.cpp b/test/tile/raster_dem_tile.test.cpp
new file mode 100644
index 0000000000..c6f9b80b42
--- /dev/null
+++ b/test/tile/raster_dem_tile.test.cpp
@@ -0,0 +1,93 @@
+#include <mbgl/test/util.hpp>
+#include <mbgl/test/fake_file_source.hpp>
+#include <mbgl/tile/raster_dem_tile.hpp>
+#include <mbgl/tile/tile_loader_impl.hpp>
+
+#include <mbgl/style/style.hpp>
+#include <mbgl/util/default_thread_pool.hpp>
+#include <mbgl/util/run_loop.hpp>
+#include <mbgl/map/transform.hpp>
+#include <mbgl/annotation/annotation_manager.hpp>
+#include <mbgl/renderer/tile_parameters.hpp>
+#include <mbgl/renderer/buckets/hillshade_bucket.hpp>
+#include <mbgl/renderer/image_manager.hpp>
+#include <mbgl/text/glyph_manager.hpp>
+
+using namespace mbgl;
+
+class RasterDEMTileTest {
+public:
+ FakeFileSource fileSource;
+ TransformState transformState;
+ util::RunLoop loop;
+ ThreadPool threadPool { 1 };
+ style::Style style { loop, fileSource, 1 };
+ AnnotationManager annotationManager { style };
+ ImageManager imageManager;
+ GlyphManager glyphManager { fileSource };
+ Tileset tileset { { "https://example.com" }, { 0, 22 }, "none" };
+
+ TileParameters tileParameters {
+ 1.0,
+ MapDebugOptions(),
+ transformState,
+ threadPool,
+ fileSource,
+ MapMode::Continuous,
+ annotationManager,
+ imageManager,
+ glyphManager,
+ 0
+ };
+};
+
+TEST(RasterDEMTile, setError) {
+ RasterDEMTileTest test;
+ RasterDEMTile tile(OverscaledTileID(0, 0, 0), test.tileParameters, test.tileset);
+ tile.setError(std::make_exception_ptr(std::runtime_error("test")));
+ EXPECT_FALSE(tile.isRenderable());
+ EXPECT_TRUE(tile.isLoaded());
+ EXPECT_TRUE(tile.isComplete());
+}
+
+TEST(RasterDEMTile, onError) {
+ RasterDEMTileTest test;
+ RasterDEMTile tile(OverscaledTileID(0, 0, 0), test.tileParameters, test.tileset);
+ tile.onError(std::make_exception_ptr(std::runtime_error("test")), 0);
+ EXPECT_FALSE(tile.isRenderable());
+ EXPECT_TRUE(tile.isLoaded());
+ EXPECT_TRUE(tile.isComplete());
+}
+
+TEST(RasterDEMTile, onParsed) {
+ RasterDEMTileTest test;
+ RasterDEMTile tile(OverscaledTileID(0, 0, 0), test.tileParameters, test.tileset);
+ tile.onParsed(std::make_unique<HillshadeBucket>(PremultipliedImage({16, 16})), 0);
+ EXPECT_TRUE(tile.isRenderable());
+ EXPECT_TRUE(tile.isLoaded());
+ EXPECT_TRUE(tile.isComplete());
+
+ // Make sure that once we've had a renderable tile and then receive erroneous data, we retain
+ // the previously rendered data and keep the tile renderable.
+ tile.setError(std::make_exception_ptr(std::runtime_error("Connection offline")));
+ EXPECT_TRUE(tile.isRenderable());
+ EXPECT_TRUE(tile.isLoaded());
+ EXPECT_TRUE(tile.isComplete());
+
+ // Then simulate a parsing failure and make sure that we keep it renderable in this situation
+ // as well.
+ tile.onError(std::make_exception_ptr(std::runtime_error("Parse error")), 0);
+ ASSERT_TRUE(tile.isRenderable());
+ EXPECT_TRUE(tile.isLoaded());
+ EXPECT_TRUE(tile.isComplete());
+}
+
+TEST(RasterDEMTile, onParsedEmpty) {
+ RasterDEMTileTest test;
+ RasterDEMTile tile(OverscaledTileID(0, 0, 0), test.tileParameters, test.tileset);
+ tile.onParsed(nullptr, 0);
+ EXPECT_FALSE(tile.isRenderable());
+ EXPECT_TRUE(tile.isLoaded());
+ EXPECT_TRUE(tile.isComplete());
+}
+