diff options
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 ℑ + } + + 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'>© 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()); +} + |