diff --git a/.github/workflows/node-ci.yml b/.github/workflows/node-ci.yml index 8e299225e..758d213ff 100644 --- a/.github/workflows/node-ci.yml +++ b/.github/workflows/node-ci.yml @@ -106,11 +106,11 @@ jobs: persist-credentials: false - name: Get OS Architecture - if: runner.os == 'MacOS' || runner.os == 'Linux' + if: runner.os == 'macOS' || runner.os == 'Linux' run: uname -m - name: Install dependencies (MacOS) - if: runner.os == 'MacOS' + if: runner.os == 'macOS' env: HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 @@ -200,7 +200,7 @@ jobs: key: ${{ runner.os }}-${{ runner.arch }}-cmake-node-module-deps - name: Configure maplibre-native (MacOS) - if: runner.os == 'MacOS' + if: runner.os == 'macOS' run: | cmake --preset macos-metal-node -DCMAKE_BUILD_TYPE=${{ env.BUILDTYPE }} @@ -242,7 +242,7 @@ jobs: cmake --preset windows-arm64-opengl-node -DCMAKE_BUILD_TYPE=${{ env.BUILDTYPE }} - name: Build maplibre-native (MacOS/Linux) - if: runner.os == 'MacOS' || runner.os == 'Linux' + if: runner.os == 'macOS' || runner.os == 'Linux' run: | cmake --build build -j "$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null)" @@ -254,7 +254,7 @@ jobs: - name: Run render tests on macOS id: render_tests if: runner.os == 'macOS' - continue-on-error: ${{ runner.arch == 'X64' }} + continue-on-error: true run: ./build/mbgl-render-test-runner --manifestPath metrics/macos-xcode11-release-style.json - name: Upload render test artifacts (MacOS) @@ -263,6 +263,11 @@ jobs: with: name: render-query-test-results_${{ runner.os }}_${{ matrix.arch }} path: metrics/macos-xcode11-release-style.html + if-no-files-found: ignore + + - name: Fail macOS render tests when required + if: runner.os == 'macOS' && steps.render_tests.outcome == 'failure' && runner.arch != 'X64' + run: exit 1 - name: Test (Linux) working-directory: platform/node @@ -270,7 +275,7 @@ jobs: run: xvfb-run --auto-servernum npm test - name: Test (MacOS) - if: runner.os == 'MacOS' + if: runner.os == 'macOS' continue-on-error: true # flaky on CI working-directory: platform/node run: npm test diff --git a/CMakeLists.txt b/CMakeLists.txt index d99865e32..9ea257888 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,6 +172,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/dynamic_texture_atlas.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/gpu_expression.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/hillshade_prepare_drawable_data.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/color_relief_drawable_data.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/image_drawable_data.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/line_drawable_data.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/gfx/symbol_drawable_data.hpp @@ -251,6 +252,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/fill_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/heatmap_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/hillshade_layer_factory.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/color_relief_layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/layer_factory.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/layer_manager.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/line_layer_factory.hpp @@ -362,6 +364,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/fill_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/heatmap_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/hillshade_layer.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/color_relief_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/line_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/location_indicator_layer.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/raster_layer.hpp @@ -501,6 +504,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/fill_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/heatmap_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/hillshade_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/color_relief_layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/layer_factory.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/layer_manager.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/layermanager/line_layer_factory.cpp @@ -582,6 +586,10 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_heatmap_layer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_hillshade_layer.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_hillshade_layer.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_color_relief_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_color_relief_layer.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/color_relief_layer_tweaker.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/color_relief_layer_tweaker.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_line_layer.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_line_layer.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layers/render_raster_layer.cpp @@ -680,6 +688,7 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/light.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/position.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/property_value.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/hillshade_conversions.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/rotation.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/source.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/conversion/source_options.cpp @@ -765,6 +774,11 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/hillshade_layer_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/hillshade_layer_properties.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/hillshade_layer_properties.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/color_relief_layer.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/color_relief_layer_impl.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/color_relief_layer_impl.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/color_relief_layer_properties.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/color_relief_layer_properties.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/line_layer.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/line_layer_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/line_layer_impl.hpp @@ -1126,7 +1140,7 @@ target_link_libraries( mbgl-vendor-unique_resource mbgl-vendor-vector-tile mbgl-vendor-wagyu - mlt-cpp + mlt-cpp $<$:mbgl-vendor-metal-cpp> $,mbgl-rustutils,mbgl-vendor-csscolorparser> $<$:mbgl-freetype> diff --git a/bazel/core.bzl b/bazel/core.bzl index 6ff3334cd..21cf75e4b 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -23,6 +23,7 @@ MLN_PUBLIC_GENERATED_STYLE_HEADERS = [ "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/color_relief_layer.hpp", "include/mbgl/style/layers/background_layer.hpp", "include/mbgl/style/layers/location_indicator_layer.hpp", "include/mbgl/style/light.hpp", @@ -37,6 +38,7 @@ MLN_PRIVATE_GENERATED_STYLE_HEADERS = [ "src/mbgl/style/layers/fill_extrusion_layer_properties.hpp", "src/mbgl/style/layers/raster_layer_properties.hpp", "src/mbgl/style/layers/hillshade_layer_properties.hpp", + "src/mbgl/style/layers/color_relief_layer_properties.hpp", "src/mbgl/style/layers/background_layer_properties.hpp", "src/mbgl/style/layers/location_indicator_layer_properties.hpp", ] @@ -58,6 +60,8 @@ MLN_GENERATED_STYLE_SOURCE = [ "src/mbgl/style/layers/raster_layer.cpp", "src/mbgl/style/layers/hillshade_layer_properties.cpp", "src/mbgl/style/layers/hillshade_layer.cpp", + "src/mbgl/style/layers/color_relief_layer_properties.cpp", + "src/mbgl/style/layers/color_relief_layer.cpp", "src/mbgl/style/layers/background_layer_properties.cpp", "src/mbgl/style/layers/background_layer.cpp", "src/mbgl/style/layers/location_indicator_layer_properties.cpp", @@ -95,6 +99,7 @@ MLN_GENERATED_OPENGL_SHADER_HEADERS = [ "include/mbgl/shaders/gl/heatmap_texture.hpp", "include/mbgl/shaders/gl/hillshade_prepare.hpp", "include/mbgl/shaders/gl/hillshade.hpp", + "include/mbgl/shaders/gl/color_relief.hpp", "include/mbgl/shaders/gl/line_gradient.hpp", "include/mbgl/shaders/gl/line_pattern.hpp", "include/mbgl/shaders/gl/line_sdf.hpp", @@ -161,6 +166,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/layermanager/fill_layer_factory.cpp", "src/mbgl/layermanager/heatmap_layer_factory.cpp", "src/mbgl/layermanager/hillshade_layer_factory.cpp", + "src/mbgl/layermanager/color_relief_layer_factory.cpp", "src/mbgl/layermanager/layer_factory.cpp", "src/mbgl/layermanager/layer_manager.cpp", "src/mbgl/layermanager/line_layer_factory.cpp", @@ -237,6 +243,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/renderer/layers/render_heatmap_layer.hpp", "src/mbgl/renderer/layers/render_hillshade_layer.cpp", "src/mbgl/renderer/layers/render_hillshade_layer.hpp", + "src/mbgl/renderer/layers/render_color_relief_layer.cpp", + "src/mbgl/renderer/layers/render_color_relief_layer.hpp", "src/mbgl/renderer/layers/render_line_layer.cpp", "src/mbgl/renderer/layers/render_line_layer.hpp", "src/mbgl/renderer/layers/render_location_indicator_layer.cpp", @@ -336,6 +344,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/conversion/light.cpp", "src/mbgl/style/conversion/position.cpp", "src/mbgl/style/conversion/property_value.cpp", + "src/mbgl/style/conversion/hillshade_conversions.cpp", "src/mbgl/style/conversion/rotation.cpp", "src/mbgl/style/conversion/source.cpp", "src/mbgl/style/conversion/source_options.cpp", @@ -409,6 +418,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/style/layers/heatmap_layer_impl.hpp", "src/mbgl/style/layers/hillshade_layer_impl.cpp", "src/mbgl/style/layers/hillshade_layer_impl.hpp", + "src/mbgl/style/layers/color_relief_layer_impl.cpp", + "src/mbgl/style/layers/color_relief_layer_impl.hpp", "src/mbgl/style/layers/line_layer_impl.cpp", "src/mbgl/style/layers/line_layer_impl.hpp", "src/mbgl/style/layers/location_indicator_layer_impl.cpp", @@ -652,6 +663,7 @@ MLN_CORE_HEADERS = [ "include/mbgl/layermanager/fill_layer_factory.hpp", "include/mbgl/layermanager/heatmap_layer_factory.hpp", "include/mbgl/layermanager/hillshade_layer_factory.hpp", + "include/mbgl/layermanager/color_relief_layer_factory.hpp", "include/mbgl/layermanager/layer_factory.hpp", "include/mbgl/layermanager/layer_manager.hpp", "include/mbgl/layermanager/line_layer_factory.hpp", @@ -912,6 +924,7 @@ MLN_DRAWABLES_SOURCE = [ "src/mbgl/gfx/dynamic_texture_atlas.cpp", "src/mbgl/gfx/gpu_expression.cpp", "src/mbgl/gfx/hillshade_prepare_drawable_data.hpp", + "src/mbgl/gfx/color_relief_drawable_data.hpp", "src/mbgl/gfx/image_drawable_data.hpp", "src/mbgl/gfx/line_drawable_data.hpp", "src/mbgl/gfx/symbol_drawable_data.hpp", @@ -927,6 +940,8 @@ MLN_DRAWABLES_SOURCE = [ "src/mbgl/renderer/layers/background_layer_tweaker.hpp", "src/mbgl/renderer/layers/circle_layer_tweaker.cpp", "src/mbgl/renderer/layers/circle_layer_tweaker.hpp", + "src/mbgl/renderer/layers/color_relief_layer_tweaker.cpp", + "src/mbgl/renderer/layers/color_relief_layer_tweaker.hpp", "src/mbgl/renderer/layers/fill_layer_tweaker.cpp", "src/mbgl/renderer/layers/fill_layer_tweaker.hpp", "src/mbgl/renderer/layers/fill_extrusion_layer_tweaker.cpp", @@ -987,6 +1002,7 @@ MLN_DRAWABLES_HEADERS = [ "include/mbgl/shaders/heatmap_layer_ubo.hpp", "include/mbgl/shaders/heatmap_texture_layer_ubo.hpp", "include/mbgl/shaders/hillshade_layer_ubo.hpp", + "include/mbgl/shaders/color_relief_layer_ubo.hpp", "include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp", "include/mbgl/shaders/layer_ubo.hpp", "include/mbgl/shaders/line_layer_ubo.hpp", @@ -1063,6 +1079,7 @@ MLN_DRAWABLES_MTL_SOURCE = [ "src/mbgl/shaders/mtl/heatmap_texture.cpp", "src/mbgl/shaders/mtl/hillshade.cpp", "src/mbgl/shaders/mtl/hillshade_prepare.cpp", + "src/mbgl/shaders/mtl/color_relief.cpp", "src/mbgl/shaders/mtl/line.cpp", "src/mbgl/shaders/mtl/location_indicator.cpp", "src/mbgl/shaders/mtl/raster.cpp", @@ -1105,6 +1122,7 @@ MLN_DRAWABLES_MTL_HEADERS = [ "include/mbgl/shaders/mtl/heatmap_texture.hpp", "include/mbgl/shaders/mtl/hillshade.hpp", "include/mbgl/shaders/mtl/hillshade_prepare.hpp", + "include/mbgl/shaders/mtl/color_relief.hpp", "include/mbgl/shaders/mtl/line.hpp", "include/mbgl/shaders/mtl/location_indicator.hpp", "include/mbgl/shaders/mtl/raster.hpp", @@ -1150,6 +1168,7 @@ MLN_DRAWABLES_WEBGPU_SOURCE = [ "src/mbgl/shaders/webgpu/heatmap_texture.cpp", "src/mbgl/shaders/webgpu/hillshade.cpp", "src/mbgl/shaders/webgpu/hillshade_prepare.cpp", + "src/mbgl/shaders/webgpu/color_relief.cpp", "src/mbgl/shaders/webgpu/line.cpp", "src/mbgl/shaders/webgpu/location_indicator.cpp", "src/mbgl/shaders/webgpu/raster.cpp", @@ -1192,6 +1211,7 @@ MLN_DRAWABLES_WEBGPU_HEADERS = [ "include/mbgl/shaders/webgpu/heatmap_texture.hpp", "include/mbgl/shaders/webgpu/hillshade.hpp", "include/mbgl/shaders/webgpu/hillshade_prepare.hpp", + "include/mbgl/shaders/webgpu/color_relief.hpp", "include/mbgl/shaders/webgpu/line.hpp", "include/mbgl/shaders/webgpu/location_indicator.hpp", "include/mbgl/shaders/webgpu/raster.hpp", diff --git a/cmake/metal.cmake b/cmake/metal.cmake index e456e2b9c..1a36178d2 100644 --- a/cmake/metal.cmake +++ b/cmake/metal.cmake @@ -44,6 +44,7 @@ list(APPEND ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/heatmap_texture.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/hillshade.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/hillshade_prepare.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/color_relief.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/line.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/location_indicator.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/raster.hpp @@ -86,6 +87,7 @@ list(APPEND ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/heatmap_texture.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/hillshade.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/hillshade_prepare.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/color_relief.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/line.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/location_indicator.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/raster.cpp diff --git a/cmake/opengl.cmake b/cmake/opengl.cmake index 7edfe766e..f9b23e00f 100644 --- a/cmake/opengl.cmake +++ b/cmake/opengl.cmake @@ -37,6 +37,7 @@ list(APPEND ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/heatmap_texture.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/hillshade_prepare.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/hillshade.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/color_relief.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/line.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/line_gradient.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/line_pattern.hpp @@ -113,6 +114,7 @@ list(APPEND INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/heatmap_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/heatmap_texture_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/hillshade_layer_ubo.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/color_relief_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/layer_ubo.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/line_layer_ubo.hpp diff --git a/cmake/vulkan.cmake b/cmake/vulkan.cmake index 35a86ea43..36378817c 100644 --- a/cmake/vulkan.cmake +++ b/cmake/vulkan.cmake @@ -47,6 +47,7 @@ list(APPEND ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/heatmap_texture.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/hillshade.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/hillshade_prepare.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/color_relief.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/line.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/location_indicator.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/raster.hpp @@ -91,6 +92,7 @@ list(APPEND ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/heatmap_texture.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/hillshade.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/hillshade_prepare.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/color_relief.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/line.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/location_indicator.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/raster.cpp diff --git a/cmake/webgpu.cmake b/cmake/webgpu.cmake index 31482fe66..0b6bd0bde 100644 --- a/cmake/webgpu.cmake +++ b/cmake/webgpu.cmake @@ -36,6 +36,7 @@ list(APPEND ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/heatmap_texture.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/hillshade.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/hillshade_prepare.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/color_relief.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/line.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/location_indicator.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/raster.cpp @@ -75,6 +76,7 @@ list(APPEND ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/heatmap_texture.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/hillshade.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/hillshade_prepare.hpp + ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/color_relief.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/line.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/location_indicator.hpp ${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/raster.hpp diff --git a/include/mbgl/layermanager/color_relief_layer_factory.hpp b/include/mbgl/layermanager/color_relief_layer_factory.hpp new file mode 100644 index 000000000..4e5222c11 --- /dev/null +++ b/include/mbgl/layermanager/color_relief_layer_factory.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace mbgl { + +class ColorReliefLayerFactory : public LayerFactory { +protected: + const style::LayerTypeInfo* getTypeInfo() const noexcept final; + std::unique_ptr createLayer(const std::string& id, + const style::conversion::Convertible& value) noexcept final; + std::unique_ptr createRenderLayer(Immutable) noexcept final; +}; + +} // namespace mbgl diff --git a/include/mbgl/shaders/color_relief_layer_ubo.hpp b/include/mbgl/shaders/color_relief_layer_ubo.hpp new file mode 100644 index 000000000..02a37b9f8 --- /dev/null +++ b/include/mbgl/shaders/color_relief_layer_ubo.hpp @@ -0,0 +1,32 @@ +#pragma once +#include + +namespace mbgl { +namespace shaders { + +struct alignas(16) ColorReliefDrawableUBO { + /* 0 */ std::array matrix; + /* 64 */ +}; +static_assert(sizeof(ColorReliefDrawableUBO) == 64); + +struct alignas(16) ColorReliefTilePropsUBO { + /* 0 */ std::array unpack; // DEM unpack vector + /* 16 */ std::array dimension; // Texture dimensions + /* 24 */ int32_t color_ramp_size; // Number of color stops + /* 28 */ float pad_tile0; // Padding for alignment + /* 32 */ +}; +static_assert(sizeof(ColorReliefTilePropsUBO) == 32); + +struct alignas(16) ColorReliefEvaluatedPropsUBO { + /* 0 */ float opacity; + /* 4 */ float pad_eval0; + /* 8 */ float pad_eval1; + /* 12 */ float pad_eval2; + /* 16 */ +}; +static_assert(sizeof(ColorReliefEvaluatedPropsUBO) == 16); + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/gl/color_relief.hpp b/include/mbgl/shaders/gl/color_relief.hpp new file mode 100644 index 000000000..20743c10a --- /dev/null +++ b/include/mbgl/shaders/gl/color_relief.hpp @@ -0,0 +1,119 @@ +// Generated code, do not modify this file! +#pragma once +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "ColorReliefShader"; + static constexpr const char* vertex = R"(layout(std140) uniform ColorReliefDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform ColorReliefTilePropsUBO { + highp vec4 u_unpack; + highp vec2 u_dimension; + highp int u_color_ramp_size; + highp float pad_tile0; +}; + +layout (location = 0) in vec2 a_pos; +layout (location = 1) in vec2 a_texture_pos; +out vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + + highp vec2 epsilon = 1.0 / u_dimension; + float scale = (u_dimension.x - 2.0) / u_dimension.x; + v_pos = (a_texture_pos / 8192.0) * scale + epsilon; + + // Handle poles + if (a_pos.y < -32767.5) v_pos.y = 0.0; + if (a_pos.y > 32766.5) v_pos.y = 1.0; +} +)"; + static constexpr const char* fragment = R"(layout(std140) uniform ColorReliefDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform ColorReliefTilePropsUBO { + highp vec4 u_unpack; + highp vec2 u_dimension; + highp int u_color_ramp_size; + highp float pad_tile0; +}; + +layout(std140) uniform ColorReliefEvaluatedPropsUBO { + highp float u_opacity; + highp float pad_eval0; + highp float pad_eval1; + highp float pad_eval2; +}; + +uniform sampler2D u_image; +uniform sampler2D u_elevation_stops; +uniform sampler2D u_color_stops; + +in vec2 v_pos; + +float getElevation(vec2 coord) { + // Convert encoded elevation value to meters + vec4 data = texture(u_image, coord) * 255.0; + data.a = -1.0; + return dot(data, u_unpack); +} + +float getElevationStop(int stop) { + // Elevation stops are plain float values, not terrain-RGB encoded + float x = (float(stop) + 0.5) / float(u_color_ramp_size); + return texture(u_elevation_stops, vec2(x, 0.5)).r; +} + +vec4 getColorStop(int stop) { + float x = (float(stop) + 0.5) / float(u_color_ramp_size); + return texture(u_color_stops, vec2(x, 0.5)); +} + +void main() { + float el = getElevation(v_pos); + + // Binary search for color stops + int r = (u_color_ramp_size - 1); + int l = 0; + + while (r - l > 1) { + int m = (r + l) / 2; + float el_m = getElevationStop(m); + if (el < el_m) { + r = m; + } else { + l = m; + } + } + + // Get elevation values for interpolation + float el_l = getElevationStop(l); + float el_r = getElevationStop(r); + + // Get colors for interpolation + vec4 color_l = getColorStop(l); + vec4 color_r = getColorStop(r); + + // Interpolate between the two colors + // Guard against division by zero when el_r == el_l + float denom = el_r - el_l; + float t = (abs(denom) < 0.0001) ? 0.0 : clamp((el - el_l) / denom, 0.0, 1.0); + fragColor = u_opacity * mix(color_l, color_r, t); + +#ifdef OVERDRAW_INSPECTOR + fragColor = vec4(1.0); +#endif +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/gl/heatmap.hpp b/include/mbgl/shaders/gl/heatmap.hpp index 9b93c798e..e945df28b 100644 --- a/include/mbgl/shaders/gl/heatmap.hpp +++ b/include/mbgl/shaders/gl/heatmap.hpp @@ -69,7 +69,7 @@ mediump float radius = u_radius; // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO // Which solves to: // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 - float S = sqrt(-2.0 * log(ZERO / weight / u_intensity / GAUSS_COEF)) / 3.0; + float S = sqrt(-2.0 * log(ZERO / (max(weight, ZERO) * max(u_intensity, ZERO) * GAUSS_COEF))) / 3.0; // Pass the varying in units of radius v_extrude = S * unscaled_extrude; diff --git a/include/mbgl/shaders/gl/hillshade.hpp b/include/mbgl/shaders/gl/hillshade.hpp index 8d1865380..6e8e28679 100644 --- a/include/mbgl/shaders/gl/hillshade.hpp +++ b/include/mbgl/shaders/gl/hillshade.hpp @@ -25,55 +25,185 @@ void main() { static constexpr const char* fragment = R"(in vec2 v_pos; uniform sampler2D u_image; -layout (std140) uniform HillshadeTilePropsUBO { +layout(std140) uniform HillshadeTilePropsUBO { highp vec2 u_latrange; - highp vec2 u_light; + highp float u_exaggeration; + highp int u_method; // Hillshade method (0: STANDARD, 1: COMBINED, 2: IGOR, 3: MULTIDIRECTIONAL, 4: BASIC) + highp int u_num_lights; // Number of light sources (1-4) + highp float u_pad0; + highp float u_pad1; + highp float u_pad2; }; - -layout (std140) uniform HillshadeEvaluatedPropsUBO { - highp vec4 u_highlight; - highp vec4 u_shadow; +layout(std140) uniform HillshadeEvaluatedPropsUBO { highp vec4 u_accent; + highp vec4 u_altitudes; // Up to 4 altitude values (in radians) + highp vec4 u_azimuths; // Up to 4 azimuth values (in radians) + highp vec4 u_shadows[4]; // Shadow colors (up to 4 lights) + highp vec4 u_highlights[4]; // Highlight colors (up to 4 lights) }; #define PI 3.141592653589793 +#define STANDARD 0 +#define COMBINED 1 +#define IGOR 2 +#define MULTIDIRECTIONAL 3 +#define BASIC 4 + +float get_aspect(vec2 deriv) { + return deriv.x != 0.0 ? + atan(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); +} + +// MapLibre's legacy hillshade algorithm (Method 0) +void standard_hillshade(vec2 deriv) { + float azimuth = u_azimuths.x + PI; + float slope = atan(0.625 * length(deriv)); + float aspect = get_aspect(deriv); + + // Note: This implementation uses u_exaggeration as intensity, though it typically should be derived from u_altitudes.x. + float intensity = u_exaggeration; + + // Scale the slope exponentially based on intensity + float base = 1.875 - intensity * 1.75; + float maxValue = 0.5 * PI; + float scaledSlope = abs(intensity - 0.5) > 1e-6 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope; + + float accent = cos(scaledSlope); + vec4 accent_color = (1.0 - accent) * u_accent * clamp(intensity * 2.0, 0.0, 1.0); + + // Calculate shadow/highlight based on aspect + float shade = abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + vec4 shade_color = mix(u_shadows[0], u_highlights[0], shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); + + fragColor = accent_color * (1.0 - shade_color.a) + shade_color; +} + +// Basic directional hillshade (Method 4) +void basic_hillshade(vec2 deriv) { + deriv = deriv * u_exaggeration * 2.0; // Exaggerate the slope derivative + float azimuth = u_azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(u_altitudes.x); + float sin_alt = sin(u_altitudes.x); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); // cang is the hillshade intensity [0, 1] + + // Blend shadow and highlight based on intensity + if (shade > 0.5) { + fragColor = u_highlights[0] * (2.0 * shade - 1.0); // Highlight strength [0, 1] + } else { + fragColor = u_shadows[0] * (1.0 - 2.0 * shade); // Shadow strength [0, 1] + } +} + +// Multidirectional hillshade (Method 3) +void multidirectional_hillshade(vec2 deriv) { + deriv = deriv * u_exaggeration * 2.0; // Exaggerate the slope derivative + fragColor = vec4(0, 0, 0, 0); + + // Iterate through all light sources (up to u_num_lights, max 4) + for (int i = 0; i < 4; i++) { + if (i >= u_num_lights) break; + + // Access altitude and azimuth from vec4 UBOs + float altitude = (i == 0) ? u_altitudes.x : (i == 1) ? u_altitudes.y : (i == 2) ? u_altitudes.z : u_altitudes.w; + float azimuth = (i == 0) ? u_azimuths.x : (i == 1) ? u_azimuths.y : (i == 2) ? u_azimuths.z : u_azimuths.w; + + float cos_alt = cos(altitude); + float sin_alt = sin(altitude); + // Negate cos/sin azimuth for correct light direction in the normal calculation + float cos_az = -cos(azimuth); + float sin_az = -sin(azimuth); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / + sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); // cang is the hillshade intensity [0, 1] + + // Accumulate shadow/highlight contribution from each light + if (shade > 0.5) { + fragColor += u_highlights[i] * (2.0 * shade - 1.0) / float(u_num_lights); + } else { + fragColor += u_shadows[i] * (1.0 - 2.0 * shade) / float(u_num_lights); + } + } +} + +// Combined shadow and highlight method (Method 1) +void combined_hillshade(vec2 deriv) { + // Only supports one light source (index 0) + deriv = deriv * u_exaggeration * 2.0; + float azimuth = u_azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(u_altitudes.x); + float sin_alt = sin(u_altitudes.x); + + // Calculate the angle between the light vector and the surface normal + float cang = acos((sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / + sqrt(1.0 + dot(deriv, deriv))); + + cang = clamp(cang, 0.0, PI / 2.0); // Clamp angle to 90 degrees (half hemisphere) + + // The "shade" and "highlight" components are calculated from cang (angle) and the magnitude of the slope + float shade = cang * atan(length(deriv)) * 4.0 / PI / PI; + float highlight = (PI / 2.0 - cang) * atan(length(deriv)) * 4.0 / PI / PI; + + fragColor = u_shadows[0] * shade + u_highlights[0] * highlight; +} + +// Igor's shadow/highlight method (Method 2) +void igor_hillshade(vec2 deriv) { + // Only supports one light source (index 0) + deriv = deriv * u_exaggeration * 2.0; + float aspect = get_aspect(deriv); + float azimuth = u_azimuths.x + PI; + + // Slope strength is magnitude of slope vector, normalized to [0, 1] + float slope_strength = atan(length(deriv)) * 2.0 / PI; + + // Aspect strength is difference between aspect and light azimuth, normalized to [0, 1] + float aspect_strength = 1.0 - abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + + float shadow_strength = slope_strength * aspect_strength; + float highlight_strength = slope_strength * (1.0 - aspect_strength); + + fragColor = u_shadows[0] * shadow_strength + u_highlights[0] * highlight_strength; +} + void main() { vec4 pixel = texture(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 + // Scale the derivative based on the mercator distortion at this latitude (v_pos.y) 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; + // The derivative is scaled back from [0, 1] texture range to world-space slope + // Texture range [0, 1] corresponds to slope range [-4, 4] (8.0 * 0.5 * deriv) + vec2 deriv = ((pixel.rg * 8.0) - 4.0) / scaleFactor; - // 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); - fragColor = accent_color * (1.0 - shade_color.a) + shade_color; + // Dispatch to the selected hillshade method + switch (u_method) { + case BASIC: + basic_hillshade(deriv); + break; + case COMBINED: + combined_hillshade(deriv); + break; + case IGOR: + igor_hillshade(deriv); + break; + case MULTIDIRECTIONAL: + multidirectional_hillshade(deriv); + break; + case STANDARD: + default: + standard_hillshade(deriv); + break; + } #ifdef OVERDRAW_INSPECTOR fragColor = vec4(1.0); diff --git a/include/mbgl/shaders/gl/hillshade_prepare.hpp b/include/mbgl/shaders/gl/hillshade_prepare.hpp index 2a2bf01b5..17244aab0 100644 --- a/include/mbgl/shaders/gl/hillshade_prepare.hpp +++ b/include/mbgl/shaders/gl/hillshade_prepare.hpp @@ -38,7 +38,6 @@ precision highp float; in vec2 v_pos; uniform sampler2D u_image; - layout (std140) uniform HillshadePrepareTilePropsUBO { highp vec4 u_unpack; highp vec2 u_dimension; @@ -50,13 +49,14 @@ float getElevation(vec2 coord, float bias) { // Convert encoded elevation value to meters vec4 data = texture(u_image, coord) * 255.0; data.a = -1.0; - return dot(data, u_unpack) / 4.0; + return dot(data, u_unpack); } void main() { vec2 epsilon = 1.0 / u_dimension; + float tileSize = u_dimension.x - 2.0; - // queried pixels: + // queried pixels (using Sobel operator kernel): // +-----------+ // | | | | // | a | b | c | @@ -81,30 +81,28 @@ void main() { 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 - u_maxzoom) * a) where a is an arbitrary value - // Here we use a=0.3 which works out to the expression below. see - // nickidlugash's awesome breakdown for more info + // Convert the raw pixel-space derivative (slope) into world-space slope. + // The conversion factor is: tileSize / (8 * meters_per_pixel). + // meters_per_pixel is calculated as pow(2.0, 28.2562 - u_zoom). + // The exaggeration factor is applied to scale the effect at lower zooms. + // 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; + float exaggerationFactor = u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3; + float exaggeration = u_zoom < 15.0 ? (u_zoom - 15.0) * exaggerationFactor : 0.0; vec2 deriv = vec2( (c + f + f + i) - (a + d + d + g), (g + h + h + i) - (a + b + b + c) - ) / pow(2.0, (u_zoom - u_maxzoom) * exaggeration + 19.2562 - u_zoom); + ) * tileSize / pow(2.0, exaggeration + (28.2562 - u_zoom)); + // Encode the derivative into the color channels (r and g) + // The derivative is scaled from world-space slope to the range [0, 1] for texture storage. + // The maximum possible world-space derivative is assumed to be 4 (hence division by 8.0). fragColor = clamp(vec4( - deriv.x / 2.0 + 0.5, - deriv.y / 2.0 + 0.5, + deriv.x / 8.0 + 0.5, + deriv.y / 8.0 + 0.5, 1.0, 1.0), 0.0, 1.0); - #ifdef OVERDRAW_INSPECTOR fragColor = vec4(1.0); #endif diff --git a/include/mbgl/shaders/gl/shader_info.hpp b/include/mbgl/shaders/gl/shader_info.hpp index 1d9c6c969..f8c111ba4 100644 --- a/include/mbgl/shaders/gl/shader_info.hpp +++ b/include/mbgl/shaders/gl/shader_info.hpp @@ -163,6 +163,13 @@ struct ShaderInfo { static const std::vector textures; }; +template <> +struct ShaderInfo { + static const std::vector attributes; + static const std::vector uniformBlocks; + static const std::vector textures; +}; + template <> struct ShaderInfo { static const std::vector attributes; diff --git a/include/mbgl/shaders/hillshade_layer_ubo.hpp b/include/mbgl/shaders/hillshade_layer_ubo.hpp index 7edee8f77..53626cb06 100644 --- a/include/mbgl/shaders/hillshade_layer_ubo.hpp +++ b/include/mbgl/shaders/hillshade_layer_ubo.hpp @@ -1,10 +1,20 @@ #pragma once - #include namespace mbgl { namespace shaders { +// Maximum number of illumination sources supported +constexpr int MAX_ILLUMINATION_SOURCES = 4; + +enum class HillshadeMethod : int32_t { + Standard = 0, + Combined = 1, + Igor = 2, + Multidirectional = 3, + Basic = 4 +}; + struct alignas(16) HillshadeDrawableUBO { /* 0 */ std::array matrix; /* 64 */ @@ -13,19 +23,26 @@ static_assert(sizeof(HillshadeDrawableUBO) == 4 * 16); struct alignas(16) HillshadeTilePropsUBO { /* 0 */ std::array latrange; - /* 8 */ std::array light; - /* 16 */ + /* 8 */ float exaggeration; // NEW: replaces light[0] (intensity) + /* 12 */ int32_t method; // NEW: hillshade method (0-4) + /* 16 */ int32_t num_lights; // NEW: number of light sources (1-4) + /* 20 */ float pad0; // Padding for alignment + /* 24 */ float pad1; // Padding for alignment + /* 28 */ float pad2; // Padding for alignment + /* 32 */ }; -static_assert(sizeof(HillshadeTilePropsUBO) == 16); +static_assert(sizeof(HillshadeTilePropsUBO) == 32); /// Evaluated properties that do not depend on the tile struct alignas(16) HillshadeEvaluatedPropsUBO { - /* 0 */ Color highlight; - /* 16 */ Color shadow; - /* 32 */ Color accent; - /* 48 */ + /* 0 */ Color accent; + /* 16 */ std::array altitudes; // NEW: altitude values in radians (up to 4) + /* 32 */ std::array azimuths; // NEW: azimuth values in radians (up to 4) + /* 48 */ std::array shadows; // NEW: 4 shadow colors (4 colors × 4 RGBA) + /* 112 */ std::array highlights; // NEW: 4 highlight colors (4 colors × 4 RGBA) + /* 176 */ }; -static_assert(sizeof(HillshadeEvaluatedPropsUBO) == 3 * 16); +static_assert(sizeof(HillshadeEvaluatedPropsUBO) == 176); } // namespace shaders } // namespace mbgl diff --git a/include/mbgl/shaders/mtl/color_relief.hpp b/include/mbgl/shaders/mtl/color_relief.hpp new file mode 100644 index 000000000..ad2628cc8 --- /dev/null +++ b/include/mbgl/shaders/mtl/color_relief.hpp @@ -0,0 +1,170 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace shaders { + +constexpr auto colorReliefShaderPrelude = R"( + +enum { + idColorReliefDrawableUBO = idDrawableReservedVertexOnlyUBO, + idColorReliefTilePropsUBO = drawableReservedUBOCount, + idColorReliefEvaluatedPropsUBO, + colorReliefUBOCount +}; + +struct alignas(16) ColorReliefDrawableUBO { + /* 0 */ float4x4 matrix; + /* 64 */ +}; +static_assert(sizeof(ColorReliefDrawableUBO) == 4 * 16, "wrong size"); + +struct alignas(16) ColorReliefTilePropsUBO { + /* 0 */ float4 unpack; + /* 16 */ float2 dimension; + /* 24 */ int32_t color_ramp_size; + /* 28 */ float pad_tile0; + /* 32 */ +}; +static_assert(sizeof(ColorReliefTilePropsUBO) == 2 * 16, "wrong size"); + +struct alignas(16) ColorReliefEvaluatedPropsUBO { + /* 0 */ float opacity; + /* 4 */ float pad_eval0; + /* 8 */ float pad_eval1; + /* 12 */ float pad_eval2; + /* 16 */ +}; +static_assert(sizeof(ColorReliefEvaluatedPropsUBO) == 16, "wrong size"); + +)"; + +template <> +struct ShaderSource { + static constexpr auto name = "ColorReliefShader"; + static constexpr auto vertexMainFunction = "vertexMain"; + static constexpr auto fragmentMainFunction = "fragmentMain"; + + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto prelude = colorReliefShaderPrelude; + static constexpr auto source = R"( + +struct VertexStage { + short2 pos [[attribute(colorReliefUBOCount + 0)]]; + short2 texture_pos [[attribute(colorReliefUBOCount + 1)]]; +}; + +struct FragmentStage { + float4 position [[position, invariant]]; + float2 pos; +}; + +FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], + device const uint32_t& uboIndex [[buffer(idGlobalUBOIndex)]], + device const ColorReliefDrawableUBO* drawableVector [[buffer(idColorReliefDrawableUBO)]], + device const ColorReliefTilePropsUBO* tilePropsVector [[buffer(idColorReliefTilePropsUBO)]]) { + + device const ColorReliefDrawableUBO& drawable = drawableVector[uboIndex]; + device const ColorReliefTilePropsUBO& tileProps = tilePropsVector[uboIndex]; + + const float4 position = drawable.matrix * float4(float2(vertx.pos), 0, 1); + + // Calculate texture coordinate + float2 epsilon = 1.0 / tileProps.dimension; + float scale = (tileProps.dimension.x - 2.0) / tileProps.dimension.x; + float2 pos = (float2(vertx.pos) / 8192.0) * scale + epsilon; + + // Handle poles (use vertx.pos, not texture_pos, to match GLSL a_pos) + if (float(vertx.pos.y) < -32767.5) pos.y = 0.0; + if (float(vertx.pos.y) > 32766.5) pos.y = 1.0; + + return { + .position = position, + .pos = pos, + }; +} + +// Function to convert terrain-RGB encoded elevation value to meters +float getElevation(float2 coord, texture2d image, sampler image_sampler, float4 unpack) { + float4 data = image.sample(image_sampler, coord) * 255.0; + data.a = -1.0; + return dot(data, unpack); +} + +// Function to get the elevation value at a specific color ramp stop +float getElevationStop(int stop, int color_ramp_size, texture2d elevationStops, sampler elevation_sampler) { + // Elevation stops are plain float values, stored in the R channel + float x = (float(stop) + 0.5) / float(color_ramp_size); + return elevationStops.sample(elevation_sampler, float2(x, 0.5)).r; +} + +// Function to get the color value at a specific color ramp stop +float4 getColorStop(int stop, int color_ramp_size, texture2d colorStops, sampler color_sampler) { + float x = (float(stop) + 0.5) / float(color_ramp_size); + return colorStops.sample(color_sampler, float2(x, 0.5)); +} + +half4 fragment fragmentMain(FragmentStage in [[stage_in]], + device const uint32_t& uboIndex [[buffer(idGlobalUBOIndex)]], + device const ColorReliefTilePropsUBO* tilePropsVector [[buffer(idColorReliefTilePropsUBO)]], + device const ColorReliefEvaluatedPropsUBO& props [[buffer(idColorReliefEvaluatedPropsUBO)]], + texture2d image [[texture(0)]], + texture2d elevationStops [[texture(1)]], + texture2d colorStops [[texture(2)]], + sampler image_sampler [[sampler(0)]], + sampler elevation_stops_sampler [[sampler(1)]], + sampler color_stops_sampler [[sampler(2)]]) { +#if defined(OVERDRAW_INSPECTOR) + return half4(1.0); +#endif + + device const ColorReliefTilePropsUBO& tileProps = tilePropsVector[uboIndex]; + + // 1. Get elevation at this pixel from DEM + float el = getElevation(in.pos, image, image_sampler, tileProps.unpack); + + // 2. Binary search to find surrounding elevation stops (l and r indices) + int r = tileProps.color_ramp_size - 1; + int l = 0; + + // Perform binary search + while (r - l > 1) { + int m = (r + l) / 2; + float el_m = getElevationStop(m, tileProps.color_ramp_size, elevationStops, elevation_stops_sampler); + + if (el < el_m) { + r = m; + } else { + l = m; + } + } + + // 3. Get the elevation values and colors at the stops + float el_l = getElevationStop(l, tileProps.color_ramp_size, elevationStops, elevation_stops_sampler); + float el_r = getElevationStop(r, tileProps.color_ramp_size, elevationStops, elevation_stops_sampler); + + float4 color_l = getColorStop(l, tileProps.color_ramp_size, colorStops, color_stops_sampler); + float4 color_r = getColorStop(r, tileProps.color_ramp_size, colorStops, color_stops_sampler); + + // 4. Interpolate color based on elevation + // Guard against division by zero when el_r == el_l + float denom = el_r - el_l; + float t = (abs(denom) < 0.0001) ? 0.0 : clamp((el - el_l) / denom, 0.0, 1.0); + + thread half4 fragColor; + float4 color = mix(color_l, color_r, t); + color *= props.opacity; + fragColor = half4(color); + return fragColor; +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/mtl/common.hpp b/include/mbgl/shaders/mtl/common.hpp index f9d1bfbab..a333ea301 100644 --- a/include/mbgl/shaders/mtl/common.hpp +++ b/include/mbgl/shaders/mtl/common.hpp @@ -23,7 +23,7 @@ using namespace metal; template inline auto glMod(T1 x, T2 y) { return x - y * metal::floor(x/y); } -float radians(float degrees) { +inline float radians(float degrees) { return M_PI_F * degrees / 180.0; } diff --git a/include/mbgl/shaders/mtl/heatmap.hpp b/include/mbgl/shaders/mtl/heatmap.hpp index ead80d8ab..520e65a2b 100644 --- a/include/mbgl/shaders/mtl/heatmap.hpp +++ b/include/mbgl/shaders/mtl/heatmap.hpp @@ -109,7 +109,7 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO // Which solves to: // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 - const float S = sqrt(-2.0 * log(ZERO / weight / props.intensity / GAUSS_COEF)) / 3.0; + const float S = sqrt(-2.0 * log(ZERO / (max(weight, ZERO) * max(props.intensity, ZERO) * GAUSS_COEF))) / 3.0; // Pass the varying in units of radius const float2 extrude = S * unscaled_extrude; diff --git a/include/mbgl/shaders/mtl/hillshade.hpp b/include/mbgl/shaders/mtl/hillshade.hpp index b50b34fd6..dc8d25000 100644 --- a/include/mbgl/shaders/mtl/hillshade.hpp +++ b/include/mbgl/shaders/mtl/hillshade.hpp @@ -24,19 +24,26 @@ static_assert(sizeof(HillshadeDrawableUBO) == 4 * 16, "wrong size"); struct alignas(16) HillshadeTilePropsUBO { /* 0 */ float2 latrange; - /* 8 */ float2 light; - /* 16 */ + /* 8 */ float exaggeration; + /* 12 */ int32_t method; + /* 16 */ int32_t num_lights; + /* 20 */ float pad0; + /* 24 */ float pad1; + /* 28 */ float pad2; + /* 32 */ }; -static_assert(sizeof(HillshadeTilePropsUBO) == 16, "wrong size"); +static_assert(sizeof(HillshadeTilePropsUBO) == 2 * 16, "wrong size"); /// Evaluated properties that do not depend on the tile struct alignas(16) HillshadeEvaluatedPropsUBO { - /* 0 */ float4 highlight; - /* 16 */ float4 shadow; - /* 32 */ float4 accent; - /* 48 */ + /* 0 */ float4 accent; + /* 16 */ float4 altitudes; // Up to 4 altitude values (in radians) + /* 32 */ float4 azimuths; // Up to 4 azimuth values (in radians) + /* 48 */ float4 shadows[4]; // Shadow colors (up to 4 lights) + /* 112 */ float4 highlights[4]; // Highlight colors (up to 4 lights) + /* 176 */ }; -static_assert(sizeof(HillshadeEvaluatedPropsUBO) == 3 * 16, "wrong size"); +static_assert(sizeof(HillshadeEvaluatedPropsUBO) == 11 * 16, "wrong size"); )"; @@ -53,6 +60,13 @@ struct ShaderSource { static constexpr auto prelude = hillshadeShaderPrelude; static constexpr auto source = R"( +#define PI 3.141592653589793 +#define STANDARD 0 +#define COMBINED 1 +#define IGOR 2 +#define MULTIDIRECTIONAL 3 +#define BASIC 4 + struct VertexStage { short2 pos [[attribute(hillshadeUBOCount + 0)]]; short2 texture_pos [[attribute(hillshadeUBOCount + 1)]]; @@ -71,6 +85,8 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], const float4 position = drawable.matrix * float4(float2(vertx.pos), 0, 1); float2 pos = float2(vertx.texture_pos) / 8192.0; + // Metal's texture coordinate origin differs from some renderers; + // restore Y-flip to match prepare pass and historical behavior. pos.y = 1.0 - pos.y; return { @@ -79,6 +95,129 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], }; } +// Helper function to calculate aspect (normalized direction of slope) +float get_aspect(float2 deriv) { + return deriv.x != 0.0 ? atan2(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); +} + +// MapLibre's legacy hillshade algorithm (Method 0: STANDARD) +void standard_hillshade(float2 deriv, device const HillshadeTilePropsUBO& tileProps, device const HillshadeEvaluatedPropsUBO& props, thread half4& fragColor) { + float azimuth = props.azimuths.x + PI; + float slope = atan(0.625 * length(deriv)); + float aspect = get_aspect(deriv); + + float intensity = tileProps.exaggeration; + + // Scale the slope exponentially based on intensity + float base = 1.875 - intensity * 1.75; + float maxValue = 0.5 * PI; + float scaledSlope = abs(intensity - 0.5) > 1e-6 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope; + + float accent = cos(scaledSlope); + float4 accent_color = (1.0 - accent) * props.accent * clamp(intensity * 2.0, 0.0, 1.0); + + float shade = abs(glMod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + float4 shade_color = mix(props.shadows[0], props.highlights[0], shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); + + fragColor = half4(accent_color * (1.0 - shade_color.a) + shade_color); +} + +// Basic directional hillshade (Method 4: BASIC) +void basic_hillshade(float2 deriv, device const HillshadeTilePropsUBO& tileProps, device const HillshadeEvaluatedPropsUBO& props, thread half4& fragColor) { + deriv = deriv * tileProps.exaggeration * 2.0; + float azimuth = props.azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(props.altitudes.x); + float sin_alt = sin(props.altitudes.x); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); + + // Blend shadow and highlight based on intensity + if (shade > 0.5) { + fragColor = half4(props.highlights[0] * (2.0 * shade - 1.0)); + } else { + fragColor = half4(props.shadows[0] * (1.0 - 2.0 * shade)); + } +} + +// Multidirectional hillshade (Method 3: MULTIDIRECTIONAL) +void multidirectional_hillshade(float2 deriv, device const HillshadeTilePropsUBO& tileProps, device const HillshadeEvaluatedPropsUBO& props, thread half4& fragColor) { + deriv = deriv * tileProps.exaggeration * 2.0; + float4 total_color = float4(0, 0, 0, 0); + + int num_lights = min(tileProps.num_lights, 4); + + // Iterate through all light sources + for (int i = 0; i < num_lights; i++) { + // Access altitude and azimuth from vec4 UBOs + float altitude = (i == 0) ? props.altitudes.x : (i == 1) ? props.altitudes.y : (i == 2) ? props.altitudes.z : props.altitudes.w; + float azimuth = (i == 0) ? props.azimuths.x : (i == 1) ? props.azimuths.y : (i == 2) ? props.azimuths.z : props.azimuths.w; + + float cos_alt = cos(altitude); + float sin_alt = sin(altitude); + // Negate cos/sin azimuth for correct light direction + float cos_az = -cos(azimuth); + float sin_az = -sin(azimuth); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); + + // Accumulate shadow/highlight contribution from each light + if (shade > 0.5) { + total_color += props.highlights[i] * (2.0 * shade - 1.0) / float(num_lights); + } else { + total_color += props.shadows[i] * (1.0 - 2.0 * shade) / float(num_lights); + } + } + + fragColor = half4(total_color); +} + +// Combined shadow and highlight method (Method 1: COMBINED) +void combined_hillshade(float2 deriv, device const HillshadeTilePropsUBO& tileProps, device const HillshadeEvaluatedPropsUBO& props, thread half4& fragColor) { + // Only supports one light source (index 0) + deriv = deriv * tileProps.exaggeration * 2.0; + float azimuth = props.azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(props.altitudes.x); + float sin_alt = sin(props.altitudes.x); + + // Calculate the angle between the light vector and the surface normal + float cang = acos((sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv))); + + cang = clamp(cang, 0.0, PI / 2.0); + + // Calculate shade and highlight components from angle and slope magnitude + float shade = cang * atan(length(deriv)) * 4.0 / PI / PI; + float highlight = (PI / 2.0 - cang) * atan(length(deriv)) * 4.0 / PI / PI; + + fragColor = half4(props.shadows[0] * shade + props.highlights[0] * highlight); +} + +// Igor's shadow/highlight method (Method 2: IGOR) +void igor_hillshade(float2 deriv, device const HillshadeTilePropsUBO& tileProps, device const HillshadeEvaluatedPropsUBO& props, thread half4& fragColor) { + // Only supports one light source (index 0) + deriv = deriv * tileProps.exaggeration * 2.0; + float aspect = get_aspect(deriv); + float azimuth = props.azimuths.x + PI; + + // Slope strength is magnitude of slope vector, normalized to [0, 1] + float slope_strength = atan(length(deriv)) * 2.0 / PI; + + // Aspect strength is difference between aspect and light azimuth, normalized to [0, 1] + float aspect_strength = 1.0 - abs(glMod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + + float shadow_strength = slope_strength * aspect_strength; + float highlight_strength = slope_strength * (1.0 - aspect_strength); + + fragColor = half4(props.shadows[0] * shadow_strength + props.highlights[0] * highlight_strength); +} + half4 fragment fragmentMain(FragmentStage in [[stage_in]], device const uint32_t& uboIndex [[buffer(idGlobalUBOIndex)]], device const HillshadeTilePropsUBO* tilePropsVector [[buffer(idHillshadeTilePropsUBO)]], @@ -90,44 +229,33 @@ half4 fragment fragmentMain(FragmentStage in [[stage_in]], #endif device const HillshadeTilePropsUBO& tileProps = tilePropsVector[uboIndex]; + thread half4 fragColor; float4 pixel = image.sample(image_sampler, in.pos); - float2 deriv = ((pixel.rg * 2.0) - 1.0); + // Scale the derivative based on the mercator distortion at this latitude + float latitude = (tileProps.latrange.x - tileProps.latrange.y) * in.pos.y + tileProps.latrange.y; + float scaleFactor = cos(radians(latitude)); - // 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((tileProps.latrange[0] - tileProps.latrange[1]) * in.pos.y + tileProps.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 ? atan2(deriv.y, -deriv.x) : M_PI_F / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); + // The derivative is scaled back from [0, 1] texture range to world-space slope + // Texture range [0, 1] corresponds to slope range [-4, 4] + float2 deriv = ((pixel.rg * 8.0) - 4.0) / scaleFactor; - float intensity = tileProps.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 = tileProps.light.y + M_PI_F; + // Dispatch to the selected hillshade method + if (tileProps.method == BASIC) { + basic_hillshade(deriv, tileProps, props, fragColor); + } else if (tileProps.method == COMBINED) { + combined_hillshade(deriv, tileProps, props, fragColor); + } else if (tileProps.method == IGOR) { + igor_hillshade(deriv, tileProps, props, fragColor); + } else if (tileProps.method == MULTIDIRECTIONAL) { + multidirectional_hillshade(deriv, tileProps, props, fragColor); + } else { + // Default to STANDARD + standard_hillshade(deriv, tileProps, props, fragColor); + } - // 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 * M_PI_F; - 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. - float4 accent_color = (1.0 - accent) * props.accent * clamp(intensity * 2.0, 0.0, 1.0); - float shade = abs(glMod((aspect + azimuth) / M_PI_F + 0.5, 2.0) - 1.0); - float4 shade_color = mix(props.shadow, props.highlight, shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); - float4 color = accent_color * (1.0 - shade_color.a) + shade_color; - - return half4(color); + return fragColor; } )"; }; diff --git a/include/mbgl/shaders/mtl/hillshade_prepare.hpp b/include/mbgl/shaders/mtl/hillshade_prepare.hpp index 6d7ddcf01..fe16327e8 100644 --- a/include/mbgl/shaders/mtl/hillshade_prepare.hpp +++ b/include/mbgl/shaders/mtl/hillshade_prepare.hpp @@ -63,7 +63,7 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], float2 epsilon = 1.0 / tileProps.dimension; float scale = (tileProps.dimension.x - 2.0) / tileProps.dimension.x; - const float2 pos = (float2(vertx.texture_pos) / 8192.0) * scale + epsilon; + float2 pos = (float2(vertx.texture_pos) / 8192.0) * scale + epsilon; return { .position = position, @@ -75,7 +75,7 @@ float getElevation(float2 coord, float bias, texture2d im // Convert encoded elevation value to meters float4 data = image.sample(image_sampler, coord) * 255.0; data.a = -1.0; - return dot(data, unpack) / 4.0; + return dot(data, unpack); } half4 fragment fragmentMain(FragmentStage in [[stage_in]], @@ -87,8 +87,9 @@ half4 fragment fragmentMain(FragmentStage in [[stage_in]], #endif float2 epsilon = 1.0 / tileProps.dimension; + float tileSize = tileProps.dimension.x - 2.0; - // queried pixels: + // queried pixels (using Sobel operator kernel): // +-----------+ // | | | | // | a | b | c | @@ -102,7 +103,6 @@ half4 fragment fragmentMain(FragmentStage in [[stage_in]], // | g | h | i | // | | | | // +-----------+ - float a = getElevation(in.pos + float2(-epsilon.x, -epsilon.y), 0.0, image, image_sampler, tileProps.unpack); float b = getElevation(in.pos + float2(0, -epsilon.y), 0.0, image, image_sampler, tileProps.unpack); float c = getElevation(in.pos + float2(epsilon.x, -epsilon.y), 0.0, image, image_sampler, tileProps.unpack); @@ -113,27 +113,24 @@ half4 fragment fragmentMain(FragmentStage in [[stage_in]], float h = getElevation(in.pos + float2(0, epsilon.y), 0.0, image, image_sampler, tileProps.unpack); float i = getElevation(in.pos + float2(epsilon.x, epsilon.y), 0.0, image, image_sampler, tileProps.unpack); - // 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 - u_maxzoom) * a) where a is an arbitrary value - // Here we use a=0.3 which works out to the expression below. see - // nickidlugash's awesome breakdown for more info - // https://github.com/mapbox/mapbox-gl-js/pull/5286#discussion_r148419556 - float exaggeration = tileProps.zoom < 2.0 ? 0.4 : tileProps.zoom < 4.5 ? 0.35 : 0.3; + // Convert the raw pixel-space derivative (slope) into world-space slope. + // The conversion factor is: tileSize / (8 * meters_per_pixel). + // meters_per_pixel is calculated as pow(2.0, 28.2562 - u_zoom). + // The exaggeration factor is applied to scale the effect at lower zooms. + float exaggerationFactor = tileProps.zoom < 2.0 ? 0.4 : tileProps.zoom < 4.5 ? 0.35 : 0.3; + float exaggeration = tileProps.zoom < 15.0 ? (tileProps.zoom - 15.0) * exaggerationFactor : 0.0; float2 deriv = float2( (c + f + f + i) - (a + d + d + g), (g + h + h + i) - (a + b + b + c) - ) / pow(2.0, (tileProps.zoom - tileProps.maxzoom) * exaggeration + 19.2562 - tileProps.zoom); + ) * tileSize / pow(2.0, exaggeration + (28.2562 - tileProps.zoom)); + // Encode the derivative into the color channels (r and g) + // The derivative is scaled from world-space slope to the range [0, 1] for texture storage. + // The maximum possible world-space derivative is assumed to be 4 (hence division by 8.0). float4 color = clamp(float4( - deriv.x / 2.0 + 0.5, - deriv.y / 2.0 + 0.5, + deriv.x / 8.0 + 0.5, + deriv.y / 8.0 + 0.5, 1.0, 1.0), 0.0, 1.0); diff --git a/include/mbgl/shaders/shader_defines.hpp b/include/mbgl/shaders/shader_defines.hpp index daee8ca31..52fe25b0f 100644 --- a/include/mbgl/shaders/shader_defines.hpp +++ b/include/mbgl/shaders/shader_defines.hpp @@ -78,6 +78,12 @@ enum { hillshadePrepareDrawableUBOCount }; +enum { + idColorReliefDrawableUBO = idDrawableReservedVertexOnlyUBO, + idColorReliefTilePropsUBO = drawableReservedUBOCount, + colorReliefDrawableUBOCount +}; + enum { idLineDrawableUBO = idDrawableReservedVertexOnlyUBO, idLineTilePropsUBO = idDrawableReservedFragmentOnlyUBO, @@ -120,6 +126,7 @@ static constexpr uint32_t layerUBOStartId = std::max({static_cast(back static_cast(heatmapTextureDrawableUBOCount), static_cast(hillshadeDrawableUBOCount), static_cast(hillshadePrepareDrawableUBOCount), + static_cast(colorReliefDrawableUBOCount), static_cast(lineDrawableUBOCount), static_cast(locationIndicatorDrawableUBOCount), static_cast(rasterDrawableUBOCount), @@ -195,6 +202,11 @@ enum { hillshadePrepareUBOCount = getLayerStartValue(hillshadePrepareDrawableUBOCount) }; +enum { + idColorReliefEvaluatedPropsUBO = getLayerStartValue(colorReliefDrawableUBOCount), + colorReliefUBOCount +}; + enum { idLineEvaluatedPropsUBO = getLayerStartValue(lineDrawableUBOCount), idLineExpressionUBO, @@ -233,6 +245,7 @@ static constexpr uint32_t maxUBOCountPerShader = std::max({static_cast static_cast(heatmapTextureUBOCount), static_cast(hillshadeUBOCount), static_cast(hillshadePrepareUBOCount), + static_cast(colorReliefUBOCount), static_cast(lineUBOCount), static_cast(locationIndicatorUBOCount), static_cast(rasterUBOCount), @@ -296,6 +309,13 @@ enum { hillshadeTextureCount }; +enum { + idColorReliefImageTexture, + idColorReliefElevationStopsTexture, + idColorReliefColorStopsTexture, + colorReliefTextureCount +}; + enum { idLocationIndicatorTexture, locationIndicatorTextureCount @@ -329,6 +349,7 @@ static constexpr uint32_t maxTextureCountPerShader = std::max({static_cast(fillExtrusionTextureCount), static_cast(heatmapTextureCount), static_cast(hillshadeTextureCount), + static_cast(colorReliefTextureCount), static_cast(lineTextureCount), static_cast(locationIndicatorTextureCount), static_cast(rasterTextureCount), @@ -429,6 +450,12 @@ enum { hillshadeVertexAttributeCount }; +enum { + idColorReliefPosVertexAttribute, + idColorReliefTexturePosVertexAttribute, + colorReliefVertexAttributeCount +}; + enum { idLinePosNormalVertexAttribute, idLineDataVertexAttribute, @@ -505,6 +532,7 @@ static constexpr uint32_t maxVertexAttributeCountPerShader = std::max({ static_cast(fillExtrusionVertexAttributeCount), static_cast(heatmapVertexAttributeCount), static_cast(hillshadeVertexAttributeCount), + static_cast(colorReliefVertexAttributeCount), static_cast(lineVertexAttributeCount), static_cast(locationIndicatorVertexAttributeCount), static_cast(rasterVertexAttributeCount), diff --git a/include/mbgl/shaders/shader_manifest.hpp b/include/mbgl/shaders/shader_manifest.hpp index 3ff0c8042..715f61a63 100644 --- a/include/mbgl/shaders/shader_manifest.hpp +++ b/include/mbgl/shaders/shader_manifest.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/include/mbgl/shaders/shader_source.hpp b/include/mbgl/shaders/shader_source.hpp index 6f3e3111b..339f0025f 100644 --- a/include/mbgl/shaders/shader_source.hpp +++ b/include/mbgl/shaders/shader_source.hpp @@ -29,6 +29,7 @@ enum class BuiltIn { HeatmapShader, HeatmapTextureShader, HillshadePrepareShader, + ColorReliefShader, HillshadeShader, LineShader, LineGradientShader, diff --git a/include/mbgl/shaders/vulkan/color_relief.hpp b/include/mbgl/shaders/vulkan/color_relief.hpp new file mode 100644 index 000000000..c5529d25f --- /dev/null +++ b/include/mbgl/shaders/vulkan/color_relief.hpp @@ -0,0 +1,167 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace shaders { + +constexpr auto colorReliefShaderPrelude = R"( + +#define idColorReliefDrawableUBO idDrawableReservedVertexOnlyUBO +#define idColorReliefTilePropsUBO drawableReservedUBOCount +#define idColorReliefEvaluatedPropsUBO layerUBOStartId + +)"; + +template <> +struct ShaderSource { + static constexpr const char* name = "ColorReliefShader"; + + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto prelude = colorReliefShaderPrelude; + + static constexpr auto vertex = R"( + +layout(location = 0) in ivec2 in_position; + +layout(push_constant) uniform Constants { + int ubo_index; +} constant; + +struct ColorReliefDrawableUBO { + mat4 matrix; +}; + +struct ColorReliefTilePropsUBO { + vec4 unpack; + vec2 dimension; + int color_ramp_size; + float pad_tile0; +}; + +layout(std140, set = LAYER_SET_INDEX, binding = idColorReliefDrawableUBO) readonly buffer ColorReliefDrawableUBOVector { + ColorReliefDrawableUBO drawable_ubo[]; +} drawableVector; + +layout(std140, set = LAYER_SET_INDEX, binding = idColorReliefTilePropsUBO) readonly buffer ColorReliefTilePropsUBOVector { + ColorReliefTilePropsUBO tile_props_ubo[]; +} tilePropsVector; + +layout(location = 0) out vec2 frag_position; + +void main() { + const ColorReliefDrawableUBO drawable = drawableVector.drawable_ubo[constant.ubo_index]; + const ColorReliefTilePropsUBO tileProps = tilePropsVector.tile_props_ubo[constant.ubo_index]; + + gl_Position = drawable.matrix * vec4(in_position, 0, 1); + applySurfaceTransform(); + + highp vec2 epsilon = 1.0 / tileProps.dimension; + float scale = (tileProps.dimension.x - 2.0) / tileProps.dimension.x; + frag_position = (vec2(in_position) / 8192.0) * scale + epsilon; + + // Handle poles (use in_position to match GLSL a_pos) + if (float(in_position.y) < -32767.5) frag_position.y = 0.0; + if (float(in_position.y) > 32766.5) frag_position.y = 1.0; +} +)"; + + static constexpr auto fragment = R"( + +layout(location = 0) in vec2 frag_position; +layout(location = 0) out vec4 out_color; + +layout(push_constant) uniform Constants { + int ubo_index; +} constant; + +struct ColorReliefTilePropsUBO { + vec4 unpack; + vec2 dimension; + int color_ramp_size; + float pad_tile0; +}; + +layout(std140, set = LAYER_SET_INDEX, binding = idColorReliefTilePropsUBO) readonly buffer ColorReliefTilePropsUBOVector { + ColorReliefTilePropsUBO tile_props_ubo[]; +} tilePropsVector; + +layout(set = LAYER_SET_INDEX, binding = idColorReliefEvaluatedPropsUBO) uniform ColorReliefEvaluatedPropsUBO { + float opacity; + float pad_eval0; + float pad_eval1; + float pad_eval2; +} props; + +layout(set = DRAWABLE_IMAGE_SET_INDEX, binding = 0) uniform sampler2D image_sampler; +layout(set = DRAWABLE_IMAGE_SET_INDEX, binding = 1) uniform sampler2D elevation_stops_sampler; +layout(set = DRAWABLE_IMAGE_SET_INDEX, binding = 2) uniform sampler2D color_stops_sampler; + +float getElevation(vec2 coord, vec4 unpack) { + // Convert encoded elevation value to meters + vec4 data = texture(image_sampler, coord) * 255.0; + data.a = -1.0; + return dot(data, unpack); +} + +float getElevationStop(int stop, int color_ramp_size) { + // Elevation stops are plain float values, not terrain-RGB encoded + float x = (float(stop) + 0.5) / float(color_ramp_size); + return texture(elevation_stops_sampler, vec2(x, 0.5)).r; +} + +vec4 getColorStop(int stop, int color_ramp_size) { + float x = (float(stop) + 0.5) / float(color_ramp_size); + return texture(color_stops_sampler, vec2(x, 0.5)); +} + +void main() { + +#if defined(OVERDRAW_INSPECTOR) + out_color = vec4(1.0); + return; +#endif + + const ColorReliefTilePropsUBO tileProps = tilePropsVector.tile_props_ubo[constant.ubo_index]; + + float el = getElevation(frag_position, tileProps.unpack); + + // Binary search to find surrounding elevation stops (l and r indices) + int r = tileProps.color_ramp_size - 1; + int l = 0; + + // Perform binary search + while (r - l > 1) { + int m = (r + l) / 2; + float el_m = getElevationStop(m, tileProps.color_ramp_size); + + if (el < el_m) { + r = m; + } else { + l = m; + } + } + + // Get elevation values and colors at the stops + float el_l = getElevationStop(l, tileProps.color_ramp_size); + float el_r = getElevationStop(r, tileProps.color_ramp_size); + + vec4 color_l = getColorStop(l, tileProps.color_ramp_size); + vec4 color_r = getColorStop(r, tileProps.color_ramp_size); + + // Interpolate color based on elevation + // Guard against division by zero when el_r == el_l + float denom = el_r - el_l; + float t = (abs(denom) < 0.0001) ? 0.0 : clamp((el - el_l) / denom, 0.0, 1.0); + + out_color = props.opacity * mix(color_l, color_r, t); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/vulkan/heatmap.hpp b/include/mbgl/shaders/vulkan/heatmap.hpp index f2235e52b..fed5d9e04 100644 --- a/include/mbgl/shaders/vulkan/heatmap.hpp +++ b/include/mbgl/shaders/vulkan/heatmap.hpp @@ -98,7 +98,7 @@ void main() { // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO // Which solves to: // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 - const float S = sqrt(-2.0 * log(ZERO / weight / props.intensity / GAUSS_COEF)) / 3.0; + const float S = sqrt(-2.0 * log(ZERO / (max(weight, ZERO) * max(props.intensity, ZERO) * GAUSS_COEF))) / 3.0; // Pass the varying in units of radius const vec2 extrude = S * unscaled_extrude; diff --git a/include/mbgl/shaders/vulkan/hillshade.hpp b/include/mbgl/shaders/vulkan/hillshade.hpp index 930676260..495a86c75 100644 --- a/include/mbgl/shaders/vulkan/hillshade.hpp +++ b/include/mbgl/shaders/vulkan/hillshade.hpp @@ -49,12 +49,19 @@ void main() { applySurfaceTransform(); frag_position = vec2(in_texture_position) / 8192.0; - frag_position.y = 1.0 - frag_position.y; // TODO check this. prepare should ignore the flip + frag_position.y = 1.0 - frag_position.y; } )"; static constexpr auto fragment = R"( +#define PI 3.141592653589793 +#define STANDARD 0 +#define COMBINED 1 +#define IGOR 2 +#define MULTIDIRECTIONAL 3 +#define BASIC 4 + layout(location = 0) in vec2 frag_position; layout(location = 0) out vec4 out_color; @@ -64,7 +71,12 @@ layout(push_constant) uniform Constants { struct HillshadeTilePropsUBO { vec2 latrange; - vec2 light; + float exaggeration; + int method; + int num_lights; + float pad0; + float pad1; + float pad2; }; layout(std140, set = LAYER_SET_INDEX, binding = idHillshadeTilePropsUBO) readonly buffer HillshadeTilePropsUBOVector { @@ -72,13 +84,140 @@ layout(std140, set = LAYER_SET_INDEX, binding = idHillshadeTilePropsUBO) readonl } tilePropsVector; layout(set = LAYER_SET_INDEX, binding = idHillshadeEvaluatedPropsUBO) uniform HillshadeEvaluatedPropsUBO { - vec4 highlight; - vec4 shadow; vec4 accent; + vec4 altitudes; + vec4 azimuths; + vec4 shadows[4]; + vec4 highlights[4]; } props; layout(set = DRAWABLE_IMAGE_SET_INDEX, binding = 0) uniform sampler2D image_sampler; +float get_aspect(vec2 deriv) { + return deriv.x != 0.0 ? atan(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); +} + +// MapLibre's legacy hillshade algorithm (Method 0: STANDARD) +void standard_hillshade(vec2 deriv, const HillshadeTilePropsUBO tileProps) { + float azimuth = props.azimuths.x + PI; + float slope = atan(0.625 * length(deriv)); + float aspect = get_aspect(deriv); + + float intensity = tileProps.exaggeration; + + // Scale the slope exponentially based on intensity + float base = 1.875 - intensity * 1.75; + float maxValue = 0.5 * PI; + float scaledSlope = abs(intensity - 0.5) > 1e-6 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope; + + float accent = cos(scaledSlope); + vec4 accent_color = (1.0 - accent) * props.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(props.shadows[0], props.highlights[0], shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); + + out_color = accent_color * (1.0 - shade_color.a) + shade_color; +} + +// Basic directional hillshade (Method 4: BASIC) +void basic_hillshade(vec2 deriv, const HillshadeTilePropsUBO tileProps) { + deriv = deriv * tileProps.exaggeration * 2.0; + float azimuth = props.azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(props.altitudes.x); + float sin_alt = sin(props.altitudes.x); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); + + // Blend shadow and highlight based on intensity + if (shade > 0.5) { + out_color = props.highlights[0] * (2.0 * shade - 1.0); + } else { + out_color = props.shadows[0] * (1.0 - 2.0 * shade); + } +} + +// Multidirectional hillshade (Method 3: MULTIDIRECTIONAL) +void multidirectional_hillshade(vec2 deriv, const HillshadeTilePropsUBO tileProps) { + deriv = deriv * tileProps.exaggeration * 2.0; + vec4 total_color = vec4(0, 0, 0, 0); + + // Access altitude and azimuth from vec4 UBOs + float altitudes[4] = float[4](props.altitudes.x, props.altitudes.y, props.altitudes.z, props.altitudes.w); + float azimuths[4] = float[4](props.azimuths.x, props.azimuths.y, props.azimuths.z, props.azimuths.w); + + int num_lights = min(tileProps.num_lights, 4); + + // Iterate through all light sources + for (int i = 0; i < num_lights; i++) { + float altitude = altitudes[i]; + float azimuth = azimuths[i]; + + float cos_alt = cos(altitude); + float sin_alt = sin(altitude); + // Negate cos/sin azimuth for correct light direction + float cos_az = -cos(azimuth); + float sin_az = -sin(azimuth); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); + + // Accumulate shadow/highlight contribution from each light + if (shade > 0.5) { + total_color += props.highlights[i] * (2.0 * shade - 1.0) / float(num_lights); + } else { + total_color += props.shadows[i] * (1.0 - 2.0 * shade) / float(num_lights); + } + } + + out_color = total_color; +} + +// Combined shadow and highlight method (Method 1: COMBINED) +void combined_hillshade(vec2 deriv, const HillshadeTilePropsUBO tileProps) { + // Only supports one light source (index 0) + deriv = deriv * tileProps.exaggeration * 2.0; + float azimuth = props.azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(props.altitudes.x); + float sin_alt = sin(props.altitudes.x); + + // Calculate the angle between the light vector and the surface normal + float cang = acos((sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv))); + + cang = clamp(cang, 0.0, PI / 2.0); + + // Calculate shade and highlight components from angle and slope magnitude + float shade = cang * atan(length(deriv)) * 4.0 / PI / PI; + float highlight = (PI / 2.0 - cang) * atan(length(deriv)) * 4.0 / PI / PI; + + out_color = props.shadows[0] * shade + props.highlights[0] * highlight; +} + +// Igor's shadow/highlight method (Method 2: IGOR) +void igor_hillshade(vec2 deriv, const HillshadeTilePropsUBO tileProps) { + // Only supports one light source (index 0) + deriv = deriv * tileProps.exaggeration * 2.0; + float aspect = get_aspect(deriv); + float azimuth = props.azimuths.x + PI; + + // Slope strength is magnitude of slope vector, normalized to [0, 1] + float slope_strength = atan(length(deriv)) * 2.0 / PI; + + // Aspect strength is difference between aspect and light azimuth, normalized to [0, 1] + float aspect_strength = 1.0 - abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + + float shadow_strength = slope_strength * aspect_strength; + float highlight_strength = slope_strength * (1.0 - aspect_strength); + + out_color = props.shadows[0] * shadow_strength + props.highlights[0] * highlight_strength; +} + void main() { #if defined(OVERDRAW_INSPECTOR) @@ -90,41 +229,26 @@ void main() { vec4 pixel = texture(image_sampler, frag_position); - 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 + // Scale the derivative based on the mercator distortion at this latitude float scaleFactor = cos(radians((tileProps.latrange[0] - tileProps.latrange[1]) * frag_position.y + tileProps.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) : M_PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); - float intensity = tileProps.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 = tileProps.light.y + M_PI; + // The derivative is scaled back from [0, 1] texture range to world-space slope + // Texture range [0, 1] corresponds to slope range [-4, 4] + vec2 deriv = ((pixel.rg * 8.0) - 4.0) / scaleFactor; - // 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 * M_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) * props.accent * clamp(intensity * 2.0, 0.0, 1.0); - float shade = abs(mod((aspect + azimuth) / M_PI + 0.5, 2.0) - 1.0); - vec4 shade_color = mix(props.shadow, props.highlight, shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); - vec4 color = accent_color * (1.0 - shade_color.a) + shade_color; - - out_color = color; + // Dispatch to the selected hillshade method + if (tileProps.method == BASIC) { + basic_hillshade(deriv, tileProps); + } else if (tileProps.method == COMBINED) { + combined_hillshade(deriv, tileProps); + } else if (tileProps.method == IGOR) { + igor_hillshade(deriv, tileProps); + } else if (tileProps.method == MULTIDIRECTIONAL) { + multidirectional_hillshade(deriv, tileProps); + } else { + // Default to STANDARD + standard_hillshade(deriv, tileProps); + } } )"; }; diff --git a/include/mbgl/shaders/vulkan/hillshade_prepare.hpp b/include/mbgl/shaders/vulkan/hillshade_prepare.hpp index a2aa699b4..dfa2286a4 100644 --- a/include/mbgl/shaders/vulkan/hillshade_prepare.hpp +++ b/include/mbgl/shaders/vulkan/hillshade_prepare.hpp @@ -69,7 +69,7 @@ float getElevation(vec2 coord, float bias, sampler2D image_sampler, vec4 unpack) // Convert encoded elevation value to meters vec4 data = texture(image_sampler, coord) * 255.0; data.a = -1.0; - return dot(data, unpack) / 4.0; + return dot(data, unpack); } void main() { @@ -80,8 +80,9 @@ void main() { #endif const vec2 epsilon = 1.0 / tileProps.dimension; + const float tileSize = tileProps.dimension.x - 2.0; - // queried pixels: + // queried pixels (using Sobel operator kernel): // +-----------+ // | | | | // | a | b | c | @@ -106,27 +107,24 @@ void main() { float h = getElevation(frag_position + vec2(0, epsilon.y), 0.0, image_sampler, tileProps.unpack); float i = getElevation(frag_position + vec2(epsilon.x, epsilon.y), 0.0, image_sampler, tileProps.unpack); - // 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 - u_maxzoom) * a) where a is an arbitrary value - // Here we use a=0.3 which works out to the expression below. see - // nickidlugash's awesome breakdown for more info - // https://github.com/mapbox/mapbox-gl-js/pull/5286#discussion_r148419556 - float exaggeration = tileProps.zoom < 2.0 ? 0.4 : tileProps.zoom < 4.5 ? 0.35 : 0.3; + // Convert the raw pixel-space derivative (slope) into world-space slope. + // The conversion factor is: tileSize / (8 * meters_per_pixel). + // meters_per_pixel is calculated as pow(2.0, 28.2562 - u_zoom). + // The exaggeration factor is applied to scale the effect at lower zooms. + float exaggerationFactor = tileProps.zoom < 2.0 ? 0.4 : tileProps.zoom < 4.5 ? 0.35 : 0.3; + float exaggeration = tileProps.zoom < 15.0 ? (tileProps.zoom - 15.0) * exaggerationFactor : 0.0; vec2 deriv = vec2( (c + f + f + i) - (a + d + d + g), (g + h + h + i) - (a + b + b + c) - ) / pow(2.0, (tileProps.zoom - tileProps.maxzoom) * exaggeration + 19.2562 - tileProps.zoom); + ) * tileSize / pow(2.0, exaggeration + (28.2562 - tileProps.zoom)); + // Encode the derivative into the color channels (r and g) + // The derivative is scaled from world-space slope to the range [0, 1] for texture storage. + // The maximum possible world-space derivative is assumed to be 4 (hence division by 8.0). out_color = clamp(vec4( - deriv.x / 2.0 + 0.5, - deriv.y / 2.0 + 0.5, + deriv.x / 8.0 + 0.5, + deriv.y / 8.0 + 0.5, 1.0, 1.0), 0.0, 1.0); } diff --git a/include/mbgl/shaders/webgpu/color_relief.hpp b/include/mbgl/shaders/webgpu/color_relief.hpp new file mode 100644 index 000000000..fc354c5e8 --- /dev/null +++ b/include/mbgl/shaders/webgpu/color_relief.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace shaders { + +template <> +struct ShaderSource { + static constexpr const char* name = "ColorReliefShader"; + static const std::array attributes; + static constexpr std::array instanceAttributes{}; + static const std::array textures; + + static constexpr auto vertex = R"( +struct VertexInput { + @location(5) position: vec2, + @location(6) texture_pos: vec2, +}; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) frag_position: vec2, +}; + +struct GlobalIndexUBO { + value: u32, + pad0: vec3, +}; + +struct ColorReliefDrawableUBO { + matrix: mat4x4, +}; + +struct ColorReliefTilePropsUBO { + unpack: vec4, + dimension: vec2, + color_ramp_size: i32, + pad_tile0: f32, +}; + +@group(0) @binding(1) var globalIndex: GlobalIndexUBO; +@group(0) @binding(2) var drawableVector: array; +@group(0) @binding(4) var tilePropsVector: array; + +@vertex +fn main(in: VertexInput) -> VertexOutput { + var out: VertexOutput; + let drawable = drawableVector[globalIndex.value]; + let tileProps = tilePropsVector[globalIndex.value]; + + out.position = drawable.matrix * vec4(f32(in.position.x), f32(in.position.y), 0.0, 1.0); + + let a_pos = vec2(f32(in.position.x), f32(in.position.y)); + let epsilon = vec2(1.0, 1.0) / tileProps.dimension; + let scale = (tileProps.dimension.x - 2.0) / tileProps.dimension.x; + out.frag_position = (a_pos / 8192.0) * scale + epsilon; + + // Handle poles + out.frag_position.y = select(out.frag_position.y, 0.0, a_pos.y < -32767.5); + out.frag_position.y = select(out.frag_position.y, 1.0, a_pos.y > 32766.5); + + return out; +} +)"; + + static constexpr auto fragment = R"( +struct FragmentInput { + @location(0) frag_position: vec2, +}; + +struct GlobalIndexUBO { + value: u32, + pad0: vec3, +}; + +struct ColorReliefTilePropsUBO { + unpack: vec4, + dimension: vec2, + color_ramp_size: i32, + pad_tile0: f32, +}; + +struct ColorReliefEvaluatedPropsUBO { + opacity: f32, + pad_eval0: f32, + pad_eval1: f32, + pad_eval2: f32, +}; + +@group(0) @binding(1) var globalIndex: GlobalIndexUBO; +@group(0) @binding(4) var tilePropsVector: array; +@group(0) @binding(5) var props: ColorReliefEvaluatedPropsUBO; +@group(1) @binding(0) var texture_sampler: sampler; +@group(1) @binding(1) var dem_texture: texture_2d; +@group(1) @binding(2) var elevation_stops_texture: texture_2d; +@group(1) @binding(3) var color_stops_texture: texture_2d; + +fn getElevation(coord: vec2, unpack: vec4) -> f32 { + var data = textureSample(dem_texture, texture_sampler, coord) * 255.0; + data.a = -1.0; + return dot(data, unpack); +} + +fn getElevationStop(stop: i32, color_ramp_size: i32) -> f32 { + // Use textureLoad for RGBA32Float texture (not filterable in WebGPU) + return textureLoad(elevation_stops_texture, vec2(stop, 0), 0).r; +} + +fn getColorStop(stop: i32, color_ramp_size: i32) -> vec4 { + let x = (f32(stop) + 0.5) / f32(color_ramp_size); + return textureSample(color_stops_texture, texture_sampler, vec2(x, 0.5)); +} + +@fragment +fn main(in: FragmentInput) -> @location(0) vec4 { +#ifdef OVERDRAW_INSPECTOR + return vec4(1.0, 1.0, 1.0, 1.0); +#endif + + let tileProps = tilePropsVector[globalIndex.value]; + let el = getElevation(in.frag_position, tileProps.unpack); + + // Binary search to find surrounding elevation stops + var r: i32 = tileProps.color_ramp_size - 1; + var l: i32 = 0; + + loop { + if (r - l <= 1) { break; } + let m = (r + l) / 2; + let el_m = getElevationStop(m, tileProps.color_ramp_size); + if (el < el_m) { + r = m; + } else { + l = m; + } + } + + // Get elevation values and colors at the stops + let el_l = getElevationStop(l, tileProps.color_ramp_size); + let el_r = getElevationStop(r, tileProps.color_ramp_size); + + let color_l = getColorStop(l, tileProps.color_ramp_size); + let color_r = getColorStop(r, tileProps.color_ramp_size); + + // Interpolate color based on elevation + // Guard against division by zero when el_r == el_l + let denom = el_r - el_l; + var t: f32 = 0.0; + if (abs(denom) >= 0.0001) { + t = clamp((el - el_l) / denom, 0.0, 1.0); + } + let final_color = mix(color_l, color_r, t) * props.opacity; + + return vec4(final_color); +} +)"; +}; + +} // namespace shaders +} // namespace mbgl diff --git a/include/mbgl/shaders/webgpu/hillshade.hpp b/include/mbgl/shaders/webgpu/hillshade.hpp index 340dee529..aa89223e4 100644 --- a/include/mbgl/shaders/webgpu/hillshade.hpp +++ b/include/mbgl/shaders/webgpu/hillshade.hpp @@ -51,6 +51,12 @@ fn main(in: VertexInput) -> VertexOutput { )"; static constexpr auto fragment = R"( +const STANDARD: i32 = 0; +const COMBINED: i32 = 1; +const IGOR: i32 = 2; +const MULTIDIRECTIONAL: i32 = 3; +const BASIC: i32 = 4; + struct FragmentInput { @location(0) tex_coord: vec2, }; @@ -62,13 +68,20 @@ struct GlobalIndexUBO { struct HillshadeTilePropsUBO { latrange: vec2, - light: vec2, + exaggeration: f32, + method: i32, + num_lights: i32, + pad0: f32, + pad1: f32, + pad2: f32, }; struct HillshadeEvaluatedPropsUBO { - highlight: vec4, - shadow: vec4, accent: vec4, + altitudes: vec4, + azimuths: vec4, + shadows: array, 4>, + highlights: array, 4>, }; @group(0) @binding(1) var globalIndex: GlobalIndexUBO; @@ -77,6 +90,108 @@ struct HillshadeEvaluatedPropsUBO { @group(1) @binding(0) var texture_sampler: sampler; @group(1) @binding(1) var hillshade_texture: texture_2d; +fn get_aspect(deriv: vec2) -> f32 { + let aspectDefault = 0.5 * PI * select(-1.0, 1.0, deriv.y > 0.0); + return select(aspectDefault, atan2(deriv.y, -deriv.x), deriv.x != 0.0); +} + +fn standard_hillshade(deriv: vec2, tileProps: HillshadeTilePropsUBO) -> vec4 { + let azimuth = props.azimuths.x + PI; + let slope = atan(0.625 * length(deriv)); + let aspect = get_aspect(deriv); + + let intensity = tileProps.exaggeration; + + let base = 1.875 - intensity * 1.75; + let maxValue = 0.5 * PI; + let denom = pow(base, maxValue) - 1.0; + let useNonLinear = abs(intensity - 0.5) > 1e-6; + let scaledSlope = select(slope, ((pow(base, slope) - 1.0) / denom) * maxValue, useNonLinear); + + let accent = cos(scaledSlope); + let accentColor = (1.0 - accent) * props.accent * clamp(intensity * 2.0, 0.0, 1.0); + + let shade = abs(glMod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + let shadeColor = mix(props.shadows[0], props.highlights[0], shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); + + return accentColor * (1.0 - shadeColor.a) + shadeColor; +} + +fn basic_hillshade(deriv: vec2, tileProps: HillshadeTilePropsUBO) -> vec4 { + let scaled_deriv = deriv * tileProps.exaggeration * 2.0; + let azimuth = props.azimuths.x + PI; + let cos_az = cos(azimuth); + let sin_az = sin(azimuth); + let cos_alt = cos(props.altitudes.x); + let sin_alt = sin(props.altitudes.x); + + let cang = (sin_alt - (scaled_deriv.y * cos_az * cos_alt - scaled_deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(scaled_deriv, scaled_deriv)); + let shade = clamp(cang, 0.0, 1.0); + + return select(props.shadows[0] * (1.0 - 2.0 * shade), + props.highlights[0] * (2.0 * shade - 1.0), + shade > 0.5); +} + +fn multidirectional_hillshade(deriv: vec2, tileProps: HillshadeTilePropsUBO) -> vec4 { + let scaled_deriv = deriv * tileProps.exaggeration * 2.0; + var total_color = vec4(0.0, 0.0, 0.0, 0.0); + + let num_lights = min(tileProps.num_lights, 4); + + for (var i: i32 = 0; i < num_lights; i = i + 1) { + let altitude = select(select(select(props.altitudes.x, props.altitudes.y, i == 1), props.altitudes.z, i == 2), props.altitudes.w, i == 3); + let azimuth = select(select(select(props.azimuths.x, props.azimuths.y, i == 1), props.azimuths.z, i == 2), props.azimuths.w, i == 3); + + let cos_alt = cos(altitude); + let sin_alt = sin(altitude); + let cos_az = -cos(azimuth); + let sin_az = -sin(azimuth); + + let cang = (sin_alt - (scaled_deriv.y * cos_az * cos_alt - scaled_deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(scaled_deriv, scaled_deriv)); + let shade = clamp(cang, 0.0, 1.0); + + if (shade > 0.5) { + total_color = total_color + props.highlights[i] * (2.0 * shade - 1.0) / f32(num_lights); + } else { + total_color = total_color + props.shadows[i] * (1.0 - 2.0 * shade) / f32(num_lights); + } + } + + return total_color; +} + +fn combined_hillshade(deriv: vec2, tileProps: HillshadeTilePropsUBO) -> vec4 { + let scaled_deriv = deriv * tileProps.exaggeration * 2.0; + let azimuth = props.azimuths.x + PI; + let cos_az = cos(azimuth); + let sin_az = sin(azimuth); + let cos_alt = cos(props.altitudes.x); + let sin_alt = sin(props.altitudes.x); + + let cang = acos((sin_alt - (scaled_deriv.y * cos_az * cos_alt - scaled_deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(scaled_deriv, scaled_deriv))); + let clamped_cang = clamp(cang, 0.0, PI / 2.0); + + let shade = clamped_cang * atan(length(scaled_deriv)) * 4.0 / PI / PI; + let highlight = (PI / 2.0 - clamped_cang) * atan(length(scaled_deriv)) * 4.0 / PI / PI; + + return props.shadows[0] * shade + props.highlights[0] * highlight; +} + +fn igor_hillshade(deriv: vec2, tileProps: HillshadeTilePropsUBO) -> vec4 { + let scaled_deriv = deriv * tileProps.exaggeration * 2.0; + let aspect = get_aspect(scaled_deriv); + let azimuth = props.azimuths.x + PI; + + let slope_strength = atan(length(scaled_deriv)) * 2.0 / PI; + let aspect_strength = 1.0 - abs(glMod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + + let shadow_strength = slope_strength * aspect_strength; + let highlight_strength = slope_strength * (1.0 - aspect_strength); + + return props.shadows[0] * shadow_strength + props.highlights[0] * highlight_strength; +} + @fragment fn main(in: FragmentInput) -> @location(0) vec4 { #ifdef OVERDRAW_INSPECTOR @@ -86,34 +201,23 @@ fn main(in: FragmentInput) -> @location(0) vec4 { let tileProps = tilePropsVector[globalIndex.value]; let pixel = textureSample(hillshade_texture, texture_sampler, in.tex_coord); - let deriv = pixel.rg * 2.0 - vec2(1.0, 1.0); - let latRange = tileProps.latrange; let latitude = (latRange.x - latRange.y) * in.tex_coord.y + latRange.y; let scaleFactor = cos(radians(latitude)); - let slope = atan(1.25 * length(deriv) / scaleFactor); - let aspectDefault = 0.5 * PI * select(-1.0, 1.0, deriv.y > 0.0); - let aspect = select(aspectDefault, atan2(deriv.y, -deriv.x), deriv.x != 0.0); - let intensity = tileProps.light.x; - let azimuth = tileProps.light.y + PI; + let deriv = ((pixel.rg * 8.0) - vec2(4.0, 4.0)) / scaleFactor; - let base = 1.875 - intensity * 1.75; - let maxValue = 0.5 * PI; - let denom = pow(base, maxValue) - 1.0; - let useLinear = abs(intensity - 0.5) < 1e-6; - let scaledSlope = select(((pow(base, slope) - 1.0) / denom) * maxValue, - slope, - useLinear); - - let accent = cos(scaledSlope); - let accentColor = (1.0 - accent) * props.accent * clamp(intensity * 2.0, 0.0, 1.0); - - let shade = abs(glMod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); - let shadeColor = mix(props.shadow, props.highlight, shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); - - let color = accentColor * (1.0 - shadeColor.a) + shadeColor; - return color; + if (tileProps.method == BASIC) { + return basic_hillshade(deriv, tileProps); + } else if (tileProps.method == COMBINED) { + return combined_hillshade(deriv, tileProps); + } else if (tileProps.method == IGOR) { + return igor_hillshade(deriv, tileProps); + } else if (tileProps.method == MULTIDIRECTIONAL) { + return multidirectional_hillshade(deriv, tileProps); + } else { + return standard_hillshade(deriv, tileProps); + } } )"; }; diff --git a/include/mbgl/shaders/webgpu/hillshade_prepare.hpp b/include/mbgl/shaders/webgpu/hillshade_prepare.hpp index a9916b887..3aa0777c7 100644 --- a/include/mbgl/shaders/webgpu/hillshade_prepare.hpp +++ b/include/mbgl/shaders/webgpu/hillshade_prepare.hpp @@ -70,7 +70,7 @@ struct HillshadePrepareTilePropsUBO { fn getElevation(coord: vec2) -> f32 { var data = textureSample(dem_texture, texture_sampler, coord) * 255.0; data.a = -1.0; - return dot(data, tileProps.unpack) / 4.0; + return dot(data, tileProps.unpack); } @fragment @@ -80,6 +80,7 @@ fn main(in: FragmentInput) -> @location(0) vec4 { #endif let epsilon = vec2(1.0, 1.0) / tileProps.dimension; + let tileSize = tileProps.dimension.x - 2.0; let a = getElevation(in.tex_coord + vec2(-epsilon.x, -epsilon.y)); let b = getElevation(in.tex_coord + vec2(0.0, -epsilon.y)); @@ -90,20 +91,24 @@ fn main(in: FragmentInput) -> @location(0) vec4 { let h = getElevation(in.tex_coord + vec2(0.0, epsilon.y)); let i = getElevation(in.tex_coord + vec2(epsilon.x, epsilon.y)); - var exaggeration = 0.3; + var exaggerationFactor = 0.3; if (tileProps.zoom < 4.5) { - exaggeration = 0.35; + exaggerationFactor = 0.35; } if (tileProps.zoom < 2.0) { - exaggeration = 0.4; + exaggerationFactor = 0.4; + } + var exaggeration = 0.0; + if (tileProps.zoom < 15.0) { + exaggeration = (tileProps.zoom - 15.0) * exaggerationFactor; } - let denom = pow(2.0, (tileProps.zoom - tileProps.maxzoom) * exaggeration + 19.2562 - tileProps.zoom); + let denom = pow(2.0, exaggeration + (28.2562 - tileProps.zoom)); let deriv = vec2((c + f + f + i) - (a + d + d + g), - (g + h + h + i) - (a + b + b + c)) / denom; + (g + h + h + i) - (a + b + b + c)) * tileSize / denom; - let color = clamp(vec4(deriv.x / 2.0 + 0.5, - deriv.y / 2.0 + 0.5, + let color = clamp(vec4(deriv.x / 8.0 + 0.5, + deriv.y / 8.0 + 0.5, 1.0, 1.0), vec4(0.0, 0.0, 0.0, 0.0), diff --git a/include/mbgl/shaders/webgpu/shader_program.hpp b/include/mbgl/shaders/webgpu/shader_program.hpp index 839f3843c..ae662dd60 100644 --- a/include/mbgl/shaders/webgpu/shader_program.hpp +++ b/include/mbgl/shaders/webgpu/shader_program.hpp @@ -66,7 +66,8 @@ public: ReadOnlyStorageBuffer, StorageBuffer, Sampler, - Texture + Texture, + UnfilterableTexture // For RGBA32Float textures that require textureLoad instead of textureSample }; struct BindingInfo { diff --git a/include/mbgl/style/conversion/constant.hpp b/include/mbgl/style/conversion/constant.hpp index a73f6f461..94da87970 100644 --- a/include/mbgl/style/conversion/constant.hpp +++ b/include/mbgl/style/conversion/constant.hpp @@ -46,6 +46,11 @@ struct Converter { std::optional operator()(const Convertible& value, Error& error) const; }; +template <> +struct Converter> { + std::optional> operator()(const Convertible& value, Error& error) const; +}; + template <> struct Converter { std::optional operator()(const Convertible& value, Error& error) const; diff --git a/include/mbgl/style/expression/expression.hpp b/include/mbgl/style/expression/expression.hpp index 3c8616e82..b9d664243 100644 --- a/include/mbgl/style/expression/expression.hpp +++ b/include/mbgl/style/expression/expression.hpp @@ -51,6 +51,15 @@ public: feature(feature_), colorRampParameter(std::move(colorRampParameter_)) {} + EvaluationContext(std::optional zoom_, + GeometryTileFeature const* feature_, + std::optional colorRampParameter_, + std::optional elevation_) noexcept + : zoom(std::move(zoom_)), + feature(feature_), + colorRampParameter(std::move(colorRampParameter_)), + elevation(std::move(elevation_)) {} + EvaluationContext& withFormattedSection(const Value* formattedSection_) noexcept { formattedSection = formattedSection_; return *this; @@ -71,10 +80,16 @@ public: return *this; }; + EvaluationContext& withElevation(float elevation_) noexcept { + elevation = elevation_; + return *this; + }; + std::optional zoom; std::optional accumulated; GeometryTileFeature const* feature = nullptr; std::optional colorRampParameter; + std::optional elevation; // Contains formatted section object, std::unordered_map. const Value* formattedSection = nullptr; const FeatureState* featureState = nullptr; @@ -195,14 +210,15 @@ enum class Kind : int32_t { enum class Dependency : uint32_t { None = 0, - Feature = 1 << 0, // Data reference - Image = 1 << 1, // Image reference (equivalent to not "runtime constant") - Zoom = 1 << 2, // Zoom level - Location = 1 << 3, // Not used yet, "distance-from-center" not supported - Bind = 1 << 4, // Create variable binding ("let") - Var = 1 << 5, // Use variable binding - Override = 1 << 6, // Property override - MaskCount = 7, + Feature = 1 << 0, // Data reference + Image = 1 << 1, // Image reference (equivalent to not "runtime constant") + Zoom = 1 << 2, // Zoom level + Location = 1 << 3, // Not used yet, "distance-from-center" not supported + Bind = 1 << 4, // Create variable binding ("let") + Var = 1 << 5, // Use variable binding + Override = 1 << 6, // Property override + Elevation = 1 << 7, // Elevation from DEM + MaskCount = 8, All = (1 << MaskCount) - 1, }; diff --git a/include/mbgl/style/layers/color_relief_layer.hpp b/include/mbgl/style/layers/color_relief_layer.hpp new file mode 100644 index 000000000..09c5df93e --- /dev/null +++ b/include/mbgl/style/layers/color_relief_layer.hpp @@ -0,0 +1,59 @@ +// clang-format off + +// This file is generated. Do not edit. + +#pragma once + +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +class TransitionOptions; + +class ColorReliefLayer final : public Layer { +public: + ColorReliefLayer(const std::string& layerID, const std::string& sourceID); + ~ColorReliefLayer() override; + + // Paint properties + + static ColorRampPropertyValue getDefaultColorReliefColor(); + const ColorRampPropertyValue& getColorReliefColor() const; + void setColorReliefColor(const ColorRampPropertyValue&); + void setColorReliefColorTransition(const TransitionOptions&); + TransitionOptions getColorReliefColorTransition() const; + + static PropertyValue getDefaultColorReliefOpacity(); + const PropertyValue& getColorReliefOpacity() const; + void setColorReliefOpacity(const PropertyValue&); + void setColorReliefOpacityTransition(const TransitionOptions&); + TransitionOptions getColorReliefOpacityTransition() const; + + // Private implementation + + class Impl; + const Impl& impl() const; + + Mutable mutableImpl() const; + ColorReliefLayer(Immutable); + std::unique_ptr cloneRef(const std::string& id) const final; + +protected: + // Dynamic properties + std::optional setPropertyInternal(const std::string& name, const conversion::Convertible& value) final; + + StyleProperty getProperty(const std::string& name) const final; + Value serialize() const final; + + Mutable mutableBaseImpl() const final; +}; + +} // namespace style +} // namespace mbgl + +// clang-format on diff --git a/include/mbgl/style/layers/hillshade_layer.hpp b/include/mbgl/style/layers/hillshade_layer.hpp index 22034737f..1a1e3a8b2 100644 --- a/include/mbgl/style/layers/hillshade_layer.hpp +++ b/include/mbgl/style/layers/hillshade_layer.hpp @@ -33,27 +33,39 @@ public: void setHillshadeExaggerationTransition(const TransitionOptions&); TransitionOptions getHillshadeExaggerationTransition() const; - static PropertyValue getDefaultHillshadeHighlightColor(); - const PropertyValue& getHillshadeHighlightColor() const; - void setHillshadeHighlightColor(const PropertyValue&); + static PropertyValue> getDefaultHillshadeHighlightColor(); + const PropertyValue>& getHillshadeHighlightColor() const; + void setHillshadeHighlightColor(const PropertyValue>&); void setHillshadeHighlightColorTransition(const TransitionOptions&); TransitionOptions getHillshadeHighlightColorTransition() const; + static PropertyValue> getDefaultHillshadeIlluminationAltitude(); + const PropertyValue>& getHillshadeIlluminationAltitude() const; + void setHillshadeIlluminationAltitude(const PropertyValue>&); + void setHillshadeIlluminationAltitudeTransition(const TransitionOptions&); + TransitionOptions getHillshadeIlluminationAltitudeTransition() const; + static PropertyValue getDefaultHillshadeIlluminationAnchor(); const PropertyValue& getHillshadeIlluminationAnchor() const; void setHillshadeIlluminationAnchor(const PropertyValue&); void setHillshadeIlluminationAnchorTransition(const TransitionOptions&); TransitionOptions getHillshadeIlluminationAnchorTransition() const; - static PropertyValue getDefaultHillshadeIlluminationDirection(); - const PropertyValue& getHillshadeIlluminationDirection() const; - void setHillshadeIlluminationDirection(const PropertyValue&); + static PropertyValue> getDefaultHillshadeIlluminationDirection(); + const PropertyValue>& getHillshadeIlluminationDirection() const; + void setHillshadeIlluminationDirection(const PropertyValue>&); void setHillshadeIlluminationDirectionTransition(const TransitionOptions&); TransitionOptions getHillshadeIlluminationDirectionTransition() const; - static PropertyValue getDefaultHillshadeShadowColor(); - const PropertyValue& getHillshadeShadowColor() const; - void setHillshadeShadowColor(const PropertyValue&); + static PropertyValue getDefaultHillshadeMethod(); + const PropertyValue& getHillshadeMethod() const; + void setHillshadeMethod(const PropertyValue&); + void setHillshadeMethodTransition(const TransitionOptions&); + TransitionOptions getHillshadeMethodTransition() const; + + static PropertyValue> getDefaultHillshadeShadowColor(); + const PropertyValue>& getHillshadeShadowColor() const; + void setHillshadeShadowColor(const PropertyValue>&); void setHillshadeShadowColorTransition(const TransitionOptions&); TransitionOptions getHillshadeShadowColorTransition() const; diff --git a/include/mbgl/style/layers/layer.hpp.ejs b/include/mbgl/style/layers/layer.hpp.ejs index 4b2232fdc..46d6b22c7 100644 --- a/include/mbgl/style/layers/layer.hpp.ejs +++ b/include/mbgl/style/layers/layer.hpp.ejs @@ -9,7 +9,7 @@ #pragma once -<% if (type === 'heatmap' || type === 'line') { -%> +<% if (type === 'heatmap' || type === 'line' || type === 'color-relief') { -%> #include <% } -%> #include diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index 3d5780935..3955514e9 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -48,6 +48,14 @@ enum class HillshadeIlluminationAnchorType : bool { Viewport }; +enum class HillshadeMethodType : uint8_t { + Standard, + Combined, + Igor, + Multidirectional, + Basic +}; + enum class TranslateAnchorType : bool { Map, Viewport diff --git a/metrics/cache-style.db b/metrics/cache-style.db index daad7c6a6..cf72e0224 100644 Binary files a/metrics/cache-style.db and b/metrics/cache-style.db differ diff --git a/metrics/integration/render-tests/color-relief/hillshade/expected.png b/metrics/integration/render-tests/color-relief/hillshade/expected.png new file mode 100644 index 000000000..558998762 Binary files /dev/null and b/metrics/integration/render-tests/color-relief/hillshade/expected.png differ diff --git a/metrics/integration/render-tests/color-relief/hillshade/style.json b/metrics/integration/render-tests/color-relief/hillshade/style.json new file mode 100644 index 000000000..5e7e4dde7 --- /dev/null +++ b/metrics/integration/render-tests/color-relief/hillshade/style.json @@ -0,0 +1,59 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "color-relief", + "type": "color-relief", + "source": "source", + "paint": { + "color-relief-opacity": 1, + "color-relief-color": [ + "interpolate", + ["linear"], + ["elevation"], + 400, "rgb(112, 209, 255)", + 494.1176471, "rgb(113, 211, 247)", + 588.2352941, "rgb(114, 212, 234)", + 682.3529412, "rgb(117, 213, 222)", + 776.4705882, "rgb(120, 214, 209)", + 870.5882353, "rgb(124, 215, 196)", + 964.7058824, "rgb(130, 215, 183)", + 1058.823529, "rgb(138, 215, 169)", + 1152.941176, "rgb(149, 214, 155)", + 1247.058824, "rgb(163, 212, 143)", + 1341.176471, "rgb(178, 209, 134)", + 1435.294118, "rgb(193, 205, 127)", + 1529.411765, "rgb(207, 202, 121)", + 1623.529412, "rgb(220, 197, 118)", + 1717.647059, "rgb(233, 193, 118)", + 1811.764706, "rgb(244, 188, 120)", + 1905.882353, "rgb(255, 183, 124)", + 2000, "rgb(255, 178, 129)" + ] + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source" + } + ] +} diff --git a/metrics/integration/render-tests/color-relief/low-zoom/expected.png b/metrics/integration/render-tests/color-relief/low-zoom/expected.png new file mode 100644 index 000000000..51d509f81 Binary files /dev/null and b/metrics/integration/render-tests/color-relief/low-zoom/expected.png differ diff --git a/metrics/integration/render-tests/color-relief/low-zoom/style.json b/metrics/integration/render-tests/color-relief/low-zoom/style.json new file mode 100644 index 000000000..36bc30165 --- /dev/null +++ b/metrics/integration/render-tests/color-relief/low-zoom/style.json @@ -0,0 +1,50 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies color-relief rendering at low zoom levels (zoom 1) where exaggeration scaling is applied" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "color-relief", + "type": "color-relief", + "source": "source", + "paint": { + "color-relief-opacity": 1, + "color-relief-color": [ + "interpolate", + ["linear"], + ["elevation"], + 0, "#006600", + 500, "#00FF00", + 1000, "#FFFF00", + 2000, "#FF8800", + 3000, "#FF0000", + 5000, "#FFFFFF" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/color-relief/opacity/expected.png b/metrics/integration/render-tests/color-relief/opacity/expected.png new file mode 100644 index 000000000..79d561dac Binary files /dev/null and b/metrics/integration/render-tests/color-relief/opacity/expected.png differ diff --git a/metrics/integration/render-tests/color-relief/opacity/style.json b/metrics/integration/render-tests/color-relief/opacity/style.json new file mode 100644 index 000000000..b4de87cac --- /dev/null +++ b/metrics/integration/render-tests/color-relief/opacity/style.json @@ -0,0 +1,43 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "color-relief", + "type": "color-relief", + "source": "source", + "paint": { + "color-relief-opacity": 0.5, + "color-relief-color": [ + "interpolate", + ["linear"], + ["elevation"], + 400, "#F00", + 800, "#AA0", + 1000, "#AF0", + 1200, "#0F0", + 1400, "#0AA", + 1600, "#00F", + 2000, "#C0C" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/color-relief/rainbow/expected.png b/metrics/integration/render-tests/color-relief/rainbow/expected.png new file mode 100644 index 000000000..2f28acbba Binary files /dev/null and b/metrics/integration/render-tests/color-relief/rainbow/expected.png differ diff --git a/metrics/integration/render-tests/color-relief/rainbow/style.json b/metrics/integration/render-tests/color-relief/rainbow/style.json new file mode 100644 index 000000000..f9b8cf2ad --- /dev/null +++ b/metrics/integration/render-tests/color-relief/rainbow/style.json @@ -0,0 +1,43 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "color-relief", + "type": "color-relief", + "source": "source", + "paint": { + "color-relief-opacity": 1, + "color-relief-color": [ + "interpolate", + ["linear"], + ["elevation"], + 400, "#F00", + 800, "#AA0", + 1000, "#AF0", + 1200, "#0F0", + 1400, "#0AA", + 1600, "#00F", + 2000, "#C0C" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/color-relief/transparency/expected.png b/metrics/integration/render-tests/color-relief/transparency/expected.png new file mode 100644 index 000000000..c8aa0bc91 Binary files /dev/null and b/metrics/integration/render-tests/color-relief/transparency/expected.png differ diff --git a/metrics/integration/render-tests/color-relief/transparency/style.json b/metrics/integration/render-tests/color-relief/transparency/style.json new file mode 100644 index 000000000..24dd5b1e6 --- /dev/null +++ b/metrics/integration/render-tests/color-relief/transparency/style.json @@ -0,0 +1,43 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "color-relief", + "type": "color-relief", + "source": "source", + "paint": { + "color-relief-opacity": 1, + "color-relief-color": [ + "interpolate", + ["linear"], + ["elevation"], + 400, "#F00C", + 800, "#AA0A", + 1000, "#AF09", + 1200, "#0F08", + 1400, "#0AA7", + 1600, "#00F6", + 2000, "#C0C4" + ] + } + } + ] +} diff --git a/metrics/integration/render-tests/combinations/background-opaque--hillshade-translucent/expected-ubuntu-22.png b/metrics/integration/render-tests/combinations/background-opaque--hillshade-translucent/expected-ubuntu-22.png new file mode 100644 index 000000000..44f43698b Binary files /dev/null and b/metrics/integration/render-tests/combinations/background-opaque--hillshade-translucent/expected-ubuntu-22.png differ diff --git a/metrics/integration/render-tests/combinations/color-relief-translucent--hillshade-translucent-low-zoom/expected.png b/metrics/integration/render-tests/combinations/color-relief-translucent--hillshade-translucent-low-zoom/expected.png new file mode 100644 index 000000000..40c59b6b3 Binary files /dev/null and b/metrics/integration/render-tests/combinations/color-relief-translucent--hillshade-translucent-low-zoom/expected.png differ diff --git a/metrics/integration/render-tests/combinations/color-relief-translucent--hillshade-translucent-low-zoom/style.json b/metrics/integration/render-tests/combinations/color-relief-translucent--hillshade-translucent-low-zoom/style.json new file mode 100644 index 000000000..0a7e0b55d --- /dev/null +++ b/metrics/integration/render-tests/combinations/color-relief-translucent--hillshade-translucent-low-zoom/style.json @@ -0,0 +1,66 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies combined hillshade and color-relief rendering at low zoom levels (zoom 1) where exaggeration scaling is applied" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "dem-hillshade": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + }, + "dem-color-relief": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "color-relief", + "type": "color-relief", + "source": "dem-color-relief", + "paint": { + "color-relief-opacity": 1, + "color-relief-color": [ + "interpolate", + ["linear"], + ["elevation"], + 0, "#006600", + 500, "#00FF00", + 1000, "#FFFF00", + 2000, "#FF8800", + 3000, "#FF0000", + 5000, "#FFFFFF" + ] + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "dem-hillshade", + "paint": { + "hillshade-method": "standard" + } + } + ] +} diff --git a/metrics/integration/render-tests/combinations/fill-opaque--hillshade-translucent/expected-ubuntu-22.png b/metrics/integration/render-tests/combinations/fill-opaque--hillshade-translucent/expected-ubuntu-22.png new file mode 100644 index 000000000..44f43698b Binary files /dev/null and b/metrics/integration/render-tests/combinations/fill-opaque--hillshade-translucent/expected-ubuntu-22.png differ diff --git a/metrics/integration/render-tests/combinations/hillshade-translucent--fill-opaque/expected-ubuntu-22.png b/metrics/integration/render-tests/combinations/hillshade-translucent--fill-opaque/expected-ubuntu-22.png new file mode 100644 index 000000000..1b8463f5e Binary files /dev/null and b/metrics/integration/render-tests/combinations/hillshade-translucent--fill-opaque/expected-ubuntu-22.png differ diff --git a/metrics/integration/render-tests/combinations/hillshade-translucent--fill-opaque/expected.png b/metrics/integration/render-tests/combinations/hillshade-translucent--fill-opaque/expected.png index 987ac0bdd..9d13779ca 100644 Binary files a/metrics/integration/render-tests/combinations/hillshade-translucent--fill-opaque/expected.png and b/metrics/integration/render-tests/combinations/hillshade-translucent--fill-opaque/expected.png differ diff --git a/metrics/integration/render-tests/combinations/hillshade-translucent--fill-translucent/expected-ubuntu-22.png b/metrics/integration/render-tests/combinations/hillshade-translucent--fill-translucent/expected-ubuntu-22.png new file mode 100644 index 000000000..3432c1633 Binary files /dev/null and b/metrics/integration/render-tests/combinations/hillshade-translucent--fill-translucent/expected-ubuntu-22.png differ diff --git a/metrics/integration/render-tests/hillshade-alt/basic-0/expected.png b/metrics/integration/render-tests/hillshade-alt/basic-0/expected.png new file mode 100644 index 000000000..30893b753 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-alt/basic-0/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-alt/basic-0/style.json b/metrics/integration/render-tests/hillshade-alt/basic-0/style.json new file mode 100644 index 000000000..cd86fa555 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-alt/basic-0/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "basic", + "hillshade-illumination-altitude": 0 + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-alt/basic-90/expected.png b/metrics/integration/render-tests/hillshade-alt/basic-90/expected.png new file mode 100644 index 000000000..2ff03d7c9 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-alt/basic-90/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-alt/basic-90/style.json b/metrics/integration/render-tests/hillshade-alt/basic-90/style.json new file mode 100644 index 000000000..cfff20b4f --- /dev/null +++ b/metrics/integration/render-tests/hillshade-alt/basic-90/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "basic", + "hillshade-illumination-altitude": 90 + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-alt/combined-60/expected.png b/metrics/integration/render-tests/hillshade-alt/combined-60/expected.png new file mode 100644 index 000000000..4073d6c8a Binary files /dev/null and b/metrics/integration/render-tests/hillshade-alt/combined-60/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-alt/combined-60/style.json b/metrics/integration/render-tests/hillshade-alt/combined-60/style.json new file mode 100644 index 000000000..9a3fc9081 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-alt/combined-60/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "combined", + "hillshade-illumination-altitude": 60 + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-low-zoom/basic/expected.png b/metrics/integration/render-tests/hillshade-low-zoom/basic/expected.png new file mode 100644 index 000000000..e4454b7a3 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-low-zoom/basic/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-low-zoom/basic/style.json b/metrics/integration/render-tests/hillshade-low-zoom/basic/style.json new file mode 100644 index 000000000..5f908e4d4 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-low-zoom/basic/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies hillshade rendering with basic method at low zoom levels (zoom 1) where exaggeration scaling is applied" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "basic" + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-low-zoom/combined/expected.png b/metrics/integration/render-tests/hillshade-low-zoom/combined/expected.png new file mode 100644 index 000000000..adfdf63cb Binary files /dev/null and b/metrics/integration/render-tests/hillshade-low-zoom/combined/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-low-zoom/combined/style.json b/metrics/integration/render-tests/hillshade-low-zoom/combined/style.json new file mode 100644 index 000000000..022169ee8 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-low-zoom/combined/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies hillshade rendering with combined method at low zoom levels (zoom 1) where exaggeration scaling is applied" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "combined" + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-low-zoom/default/expected.png b/metrics/integration/render-tests/hillshade-low-zoom/default/expected.png new file mode 100644 index 000000000..58827d805 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-low-zoom/default/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-low-zoom/default/style.json b/metrics/integration/render-tests/hillshade-low-zoom/default/style.json new file mode 100644 index 000000000..8f40a7898 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-low-zoom/default/style.json @@ -0,0 +1,36 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies hillshade rendering at low zoom levels (zoom 1) where exaggeration scaling is applied to make hillshading more visible" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source" + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-low-zoom/igor/expected.png b/metrics/integration/render-tests/hillshade-low-zoom/igor/expected.png new file mode 100644 index 000000000..b745f4765 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-low-zoom/igor/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-low-zoom/igor/style.json b/metrics/integration/render-tests/hillshade-low-zoom/igor/style.json new file mode 100644 index 000000000..8ae49395f --- /dev/null +++ b/metrics/integration/render-tests/hillshade-low-zoom/igor/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies hillshade rendering with igor method at low zoom levels (zoom 1) where exaggeration scaling is applied" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "igor" + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-low-zoom/multidirectional/expected.png b/metrics/integration/render-tests/hillshade-low-zoom/multidirectional/expected.png new file mode 100644 index 000000000..8df124d05 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-low-zoom/multidirectional/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-low-zoom/multidirectional/style.json b/metrics/integration/render-tests/hillshade-low-zoom/multidirectional/style.json new file mode 100644 index 000000000..782e018a7 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-low-zoom/multidirectional/style.json @@ -0,0 +1,42 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies hillshade rendering with multidirectional method at low zoom levels (zoom 1) where exaggeration scaling is applied" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "multidirectional", + "hillshade-highlight-color": ["#FF4000", "#FFFF00", "#40ff00", "#00FF80"], + "hillshade-shadow-color": ["#00bfff", "#0000ff", "#bf00ff", "#FF0080"], + "hillshade-illumination-direction": [270, 315, 0, 45] + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-low-zoom/standard/expected.png b/metrics/integration/render-tests/hillshade-low-zoom/standard/expected.png new file mode 100644 index 000000000..58827d805 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-low-zoom/standard/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-low-zoom/standard/style.json b/metrics/integration/render-tests/hillshade-low-zoom/standard/style.json new file mode 100644 index 000000000..069b4d341 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-low-zoom/standard/style.json @@ -0,0 +1,39 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies hillshade rendering with standard method at low zoom levels (zoom 1) where exaggeration scaling is applied" + } + }, + "center": [0, 45], + "zoom": 1, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 512 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "standard" + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-maxzoom/default/expected.png b/metrics/integration/render-tests/hillshade-maxzoom/default/expected.png new file mode 100644 index 000000000..0907e207f Binary files /dev/null and b/metrics/integration/render-tests/hillshade-maxzoom/default/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-maxzoom/default/style.json b/metrics/integration/render-tests/hillshade-maxzoom/default/style.json new file mode 100644 index 000000000..5a1f13ac7 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-maxzoom/default/style.json @@ -0,0 +1,36 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies that setting a lower maxzoom on the raster-dem source produces the same output as a higher maxzoom (hillshade-accent-color/default) when the map zoom level is lower than the maxzoom (i.e., maxzoom does not alter rendering at lower zooms)" + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 12, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source" + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-maxzoom/overzoom/expected.png b/metrics/integration/render-tests/hillshade-maxzoom/overzoom/expected.png new file mode 100644 index 000000000..22ba4fb84 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-maxzoom/overzoom/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-maxzoom/overzoom/style.json b/metrics/integration/render-tests/hillshade-maxzoom/overzoom/style.json new file mode 100644 index 000000000..599118845 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-maxzoom/overzoom/style.json @@ -0,0 +1,36 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256, + "description": "This test verifies that setting a maximum zoom on the raster-dem source lower than the layer results in overzooming the source." + } + }, + "center": [-113.26903, 35.9654], + "zoom": 13, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 12, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "white" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source" + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-methods/basic/expected.png b/metrics/integration/render-tests/hillshade-methods/basic/expected.png new file mode 100644 index 000000000..f626de6a5 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-methods/basic/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-methods/basic/style.json b/metrics/integration/render-tests/hillshade-methods/basic/style.json new file mode 100644 index 000000000..08512da40 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-methods/basic/style.json @@ -0,0 +1,38 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "basic" + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-methods/combined/expected.png b/metrics/integration/render-tests/hillshade-methods/combined/expected.png new file mode 100644 index 000000000..b5f19d63e Binary files /dev/null and b/metrics/integration/render-tests/hillshade-methods/combined/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-methods/combined/style.json b/metrics/integration/render-tests/hillshade-methods/combined/style.json new file mode 100644 index 000000000..c2e2eaa60 --- /dev/null +++ b/metrics/integration/render-tests/hillshade-methods/combined/style.json @@ -0,0 +1,38 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "combined" + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-methods/igor/expected.png b/metrics/integration/render-tests/hillshade-methods/igor/expected.png new file mode 100644 index 000000000..7ed9e5c79 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-methods/igor/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-methods/igor/style.json b/metrics/integration/render-tests/hillshade-methods/igor/style.json new file mode 100644 index 000000000..e3c4f1fac --- /dev/null +++ b/metrics/integration/render-tests/hillshade-methods/igor/style.json @@ -0,0 +1,38 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "igor" + } + } + ] +} diff --git a/metrics/integration/render-tests/hillshade-methods/multidirectional/expected.png b/metrics/integration/render-tests/hillshade-methods/multidirectional/expected.png new file mode 100644 index 000000000..d2ef9a3e6 Binary files /dev/null and b/metrics/integration/render-tests/hillshade-methods/multidirectional/expected.png differ diff --git a/metrics/integration/render-tests/hillshade-methods/multidirectional/style.json b/metrics/integration/render-tests/hillshade-methods/multidirectional/style.json new file mode 100644 index 000000000..18553477d --- /dev/null +++ b/metrics/integration/render-tests/hillshade-methods/multidirectional/style.json @@ -0,0 +1,41 @@ +{ + "version": 8, + "metadata": { + "test": { + "height": 256, + "width": 256 + } + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "multidirectional", + "hillshade-highlight-color": ["#FF4000", "#FFFF00", "#40ff00", "#00FF80"], + "hillshade-shadow-color": ["#00bfff", "#0000ff", "#bf00ff", "#FF0080"], + "hillshade-illumination-direction": [270,315,0,45] + } + } + ] +} diff --git a/metrics/integration/render-tests/runtime-styling/hillshade-array-property-mismatch/expected.png b/metrics/integration/render-tests/runtime-styling/hillshade-array-property-mismatch/expected.png new file mode 100644 index 000000000..7bcd3b9dc Binary files /dev/null and b/metrics/integration/render-tests/runtime-styling/hillshade-array-property-mismatch/expected.png differ diff --git a/metrics/integration/render-tests/runtime-styling/hillshade-array-property-mismatch/style.json b/metrics/integration/render-tests/runtime-styling/hillshade-array-property-mismatch/style.json new file mode 100644 index 000000000..de7d37280 --- /dev/null +++ b/metrics/integration/render-tests/runtime-styling/hillshade-array-property-mismatch/style.json @@ -0,0 +1,74 @@ +{ + "version": 8, + "metadata": { + "test": { + "width": 256, + "height": 256, + "description": "This test verifies that changing hillshade array properties one at a time (causing temporary length mismatch) does not cause crashes or rendering artifacts. Starts with 4 lights, changes direction to 1, then updates colors to 1. Using a dramatic 4->1 change makes any issues more visible (per HarelM's suggestion).", + "operations": [ + [ + "setPaintProperty", + "hillshade", + "hillshade-illumination-direction", + 315 + ], + [ + "wait" + ], + [ + "setPaintProperty", + "hillshade", + "hillshade-highlight-color", + "#FFFFFF" + ], + [ + "wait" + ], + [ + "setPaintProperty", + "hillshade", + "hillshade-shadow-color", + "#000000" + ], + [ + "wait" + ] + ] + } + }, + "transition": { + "duration": 0 + }, + "center": [-113.26903, 35.9654], + "zoom": 11, + "sources": { + "source": { + "type": "raster-dem", + "tiles": [ + "local://tiles/{z}-{x}-{y}.terrain.png" + ], + "maxzoom": 15, + "tileSize": 256 + } + }, + "layers": [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": "gray" + } + }, + { + "id": "hillshade", + "type": "hillshade", + "source": "source", + "paint": { + "hillshade-method": "multidirectional", + "hillshade-highlight-color": ["#FF4000", "#FFFF00", "#40ff00", "#00FF80"], + "hillshade-shadow-color": ["#00bfff", "#0000ff", "#bf00ff", "#FF0080"], + "hillshade-illumination-direction": [270, 315, 0, 45] + } + } + ] +} diff --git a/metrics/integration/tiles/0-0-0.terrain.png b/metrics/integration/tiles/0-0-0.terrain.png new file mode 100644 index 000000000..b4f2fd75f Binary files /dev/null and b/metrics/integration/tiles/0-0-0.terrain.png differ diff --git a/metrics/integration/tiles/1-0-0.terrain.png b/metrics/integration/tiles/1-0-0.terrain.png new file mode 100644 index 000000000..aa9717898 Binary files /dev/null and b/metrics/integration/tiles/1-0-0.terrain.png differ diff --git a/metrics/integration/tiles/1-1-0.terrain.png b/metrics/integration/tiles/1-1-0.terrain.png new file mode 100644 index 000000000..e5cc44158 Binary files /dev/null and b/metrics/integration/tiles/1-1-0.terrain.png differ diff --git a/metrics/integration/tiles/2-1-0.terrain.png b/metrics/integration/tiles/2-1-0.terrain.png new file mode 100644 index 000000000..1b998cb23 Binary files /dev/null and b/metrics/integration/tiles/2-1-0.terrain.png differ diff --git a/metrics/integration/tiles/2-1-1.terrain.png b/metrics/integration/tiles/2-1-1.terrain.png new file mode 100644 index 000000000..131c6273b Binary files /dev/null and b/metrics/integration/tiles/2-1-1.terrain.png differ diff --git a/metrics/integration/tiles/2-2-0.terrain.png b/metrics/integration/tiles/2-2-0.terrain.png new file mode 100644 index 000000000..65a2e702a Binary files /dev/null and b/metrics/integration/tiles/2-2-0.terrain.png differ diff --git a/metrics/integration/tiles/2-2-1.terrain.png b/metrics/integration/tiles/2-2-1.terrain.png new file mode 100644 index 000000000..149ca4b80 Binary files /dev/null and b/metrics/integration/tiles/2-2-1.terrain.png differ diff --git a/package-lock.json b/package-lock.json index 52ad93193..0a7789585 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "BSD-2-Clause", "dependencies": { "@actions/core": "^1.11.1", - "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@maplibre/maplibre-gl-style-spec": "^24.3.1", "minimatch": "^10.2.2", "npm-run-all": "^4.1.5", "simple-git": "^3.25.0" @@ -72,6 +72,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", @@ -86,6 +87,7 @@ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", @@ -270,691 +272,708 @@ } }, "node_modules/@aws-sdk/client-device-farm": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-device-farm/-/client-device-farm-3.635.0.tgz", - "integrity": "sha512-ZDdpTX8iADVIwvMeOlrTCfpIcQB16otPZK62bvH1Us9Gvq4PvRBWADXWkhSHmS7NINInxh3l+9yjI3U85krzjw==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-device-farm/-/client-device-farm-3.967.0.tgz", + "integrity": "sha512-xl3fvaQpyzGuSKos/92YwyyROzNnlW+WEsAjj1XoQ7b0wBiyz9iTdv8DgznolgAl9OqfM1bRsFcRNjf5EkpR6w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.635.0", - "@aws-sdk/client-sts": "3.635.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.635.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.632.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.632.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/credential-provider-node": "3.967.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.967.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.967.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.2", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.3", + "@smithy/middleware-retry": "^4.4.19", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.18", + "@smithy/util-defaults-mode-node": "^4.2.21", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.635.0.tgz", - "integrity": "sha512-4RP+DJZWqUka1MW2aSEzTzntY3GrDzS26D8dHZvbt2I0x+dSmlnmXiJkCxLjmti2SDVYAGL9gX6e7mLS7W55jA==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.967.0.tgz", + "integrity": "sha512-7vDlsBqd9y0dJDjCy84WMN+1r60El97IKMGlegU+l9K2+t8+Wf8bYj/J2xfm+6Ayemje6P4nkKS9tubxBLqg+A==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.635.0", - "@aws-sdk/client-sts": "3.635.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.635.0", - "@aws-sdk/middleware-bucket-endpoint": "3.620.0", - "@aws-sdk/middleware-expect-continue": "3.620.0", - "@aws-sdk/middleware-flexible-checksums": "3.620.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-location-constraint": "3.609.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-sdk-s3": "3.635.0", - "@aws-sdk/middleware-ssec": "3.609.0", - "@aws-sdk/middleware-user-agent": "3.632.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/signature-v4-multi-region": "3.635.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.632.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@aws-sdk/xml-builder": "3.609.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/eventstream-serde-browser": "^3.0.6", - "@smithy/eventstream-serde-config-resolver": "^3.0.3", - "@smithy/eventstream-serde-node": "^3.0.5", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-blob-browser": "^3.1.2", - "@smithy/hash-node": "^3.0.3", - "@smithy/hash-stream-node": "^3.1.2", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/md5-js": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.1.3", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.2", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/credential-provider-node": "3.967.0", + "@aws-sdk/middleware-bucket-endpoint": "3.966.0", + "@aws-sdk/middleware-expect-continue": "3.965.0", + "@aws-sdk/middleware-flexible-checksums": "3.967.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-location-constraint": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-sdk-s3": "3.967.0", + "@aws-sdk/middleware-ssec": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.967.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/signature-v4-multi-region": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.967.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.2", + "@smithy/eventstream-serde-browser": "^4.2.7", + "@smithy/eventstream-serde-config-resolver": "^4.3.7", + "@smithy/eventstream-serde-node": "^4.2.7", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-blob-browser": "^4.2.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/hash-stream-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/md5-js": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.3", + "@smithy/middleware-retry": "^4.4.19", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.18", + "@smithy/util-defaults-mode-node": "^4.2.21", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.7", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.635.0.tgz", - "integrity": "sha512-/Hl69+JpFUo9JNVmh2gSvMgYkE4xjd+1okiRoPBbQqjI7YBP2JWCUDP8IoEkNq3wj0vNTq0OWfn6RpZycIkAXQ==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.967.0.tgz", + "integrity": "sha512-7RgUwHcRMJtWme6kCHGUVT+Rn9GmNH+FHm34N9UgMXzUqQlzFMweE7T5E9O8nv3wIp7xFNB20ADaCw9Xdnox1Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.632.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.632.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.967.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.967.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.2", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.3", + "@smithy/middleware-retry": "^4.4.19", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.18", + "@smithy/util-defaults-mode-node": "^4.2.21", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.635.0.tgz", - "integrity": "sha512-RIwDlhzAFttB1vbpznewnPqz7h1H/2UhQLwB38yfZBwYQOxyxVfLV5j5VoUUX3jY4i4qH9wiHc7b02qeAOZY6g==", - "dev": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.635.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.632.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.632.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.635.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.635.0.tgz", - "integrity": "sha512-Al2ytE69+cbA44qHlelqhzWwbURikfF13Zkal9utIG5Q6T2c7r8p6sePN92n8l/x1v0FhJ5VTxKak+cPTE0CZQ==", - "dev": true, - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.635.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.635.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.632.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.632.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/core": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", - "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.967.0.tgz", + "integrity": "sha512-sJmuP7GrVmlbO6DpXkuf9Mbn6jGNNvy6PLawvaxVF150c8bpNk3w39rerRls6q1dot1dBFV2D29hBXMY1agNMg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^2.4.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/signature-v4": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "fast-xml-parser": "4.4.1", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/xml-builder": "3.965.0", + "@smithy/core": "^3.20.2", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.965.0.tgz", + "integrity": "sha512-9FbIyJ/Zz1AdEIrb0+Pn7wRi+F/0Y566ooepg0hDyHUzRV3ZXKjOlu3wJH3YwTz2UkdwQmldfUos2yDJps7RyA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.620.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", - "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.967.0.tgz", + "integrity": "sha512-+XWw0+f/txeMbEVRtTFZhgSw1ymH1ffaVKkdMBSnw48rfSohJElKmitCqdihagRTZpzh7m8qI6tIQ5t3OUqugw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", - "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.967.0.tgz", + "integrity": "sha512-0/GIAEv5pY5htg6IBMuYccBgzz3oS2DqHjHi396ziTrwlhbrCNX96AbNhQhzAx3LBZUk13sPfeapjyQ7G57Ekg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.635.0.tgz", - "integrity": "sha512-+OqcNhhOFFY08YHLjO9/Y1n37RKAO7LADnsJ7VTXca7IfvYh27BVBn+FdlqnyEb1MQ5ArHTY4pq3pKRIg6RW4Q==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.967.0.tgz", + "integrity": "sha512-U8dMpaM6Qf6+2Qvp1uG6OcWv1RlrZW7tQkpmzEVWH8HZTGrVHIXXju64NMtIOr7yOnNwd0CKcytuD1QG+phCwQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.635.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.635.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/credential-provider-env": "3.967.0", + "@aws-sdk/credential-provider-http": "3.967.0", + "@aws-sdk/credential-provider-login": "3.967.0", + "@aws-sdk/credential-provider-process": "3.967.0", + "@aws-sdk/credential-provider-sso": "3.967.0", + "@aws-sdk/credential-provider-web-identity": "3.967.0", + "@aws-sdk/nested-clients": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.967.0.tgz", + "integrity": "sha512-kbvZsZL6CBlfnb71zuJdJmBUFZN5utNrcziZr/DZ2olEOkA9vlmizE8i9BUIbmS7ptjgvRnmcY1A966yfhiblw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.967.0", + "@aws-sdk/nested-clients": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.635.0" + "engines": { + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.635.0.tgz", - "integrity": "sha512-bmd23mnb94S6AxmWPgqJTnvT9ONKlTx7EPafE1RNO+vUl6mHih4iyqX6ZPaRcSfaPx4U1R7H1RM8cSnafXgaBg==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.967.0.tgz", + "integrity": "sha512-WuNbHs9rfKKSVok4+OBrZf0AHfzDgFYYMxN2G/q6ZfUmY4QmiPyxV5HkNFh1rqDxS9VV6kAZPo0EBmry10idSg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.635.0", - "@aws-sdk/credential-provider-ini": "3.635.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.635.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/credential-provider-env": "3.967.0", + "@aws-sdk/credential-provider-http": "3.967.0", + "@aws-sdk/credential-provider-ini": "3.967.0", + "@aws-sdk/credential-provider-process": "3.967.0", + "@aws-sdk/credential-provider-sso": "3.967.0", + "@aws-sdk/credential-provider-web-identity": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.620.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", - "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.967.0.tgz", + "integrity": "sha512-sNCY5JDV0whsfsZ6c2+6eUwH33H7UhKbqvCPbEYlIIa8wkGjCtCyFI3zZIJHVcMKJJ3117vSUFHEkNA7g+8rtw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.635.0.tgz", - "integrity": "sha512-hO/fKyvUaGpK9zyvCnmJz70EputvGWDr2UTOn/RzvcR6UB4yXoFf0QcCMubEsE3v67EsAv6PadgOeJ0vz6IazA==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.967.0.tgz", + "integrity": "sha512-0K6kITKNytFjk1UYabYUsTThgU6TQkyW6Wmt8S5zd1A/up7NSQGpp58Rpg9GIf4amQDQwb+p9FGG7emmV8FEeA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.635.0", - "@aws-sdk/token-providers": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/client-sso": "3.967.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/token-providers": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.621.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", - "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.967.0.tgz", + "integrity": "sha512-Vkr7S2ec7q/v8i/MzkHcBEdqqfWz3lyb8FDjb+NjslEwdxC3f6XwADRZzWwV1pChfx6SbsvJXKfkcF/pKAelhA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/nested-clients": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.621.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", - "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.966.0.tgz", + "integrity": "sha512-KMPZ7gtFXErd9pMpXJMBwFlxxlGIaIQrUBfj3ea7rlrNtoVHnSI4qsoldLq5l9/Ho64KoCiICH4+qXjze8JTDQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-arn-parser": "3.966.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", - "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.965.0.tgz", + "integrity": "sha512-UBxVytsmhEmFwkBnt+aV0eAJ7uc+ouNokCqMBrQ7Oc5A77qhlcHfOgXIKz2SxqsiYTsDq+a0lWFM/XpyRWraqA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", - "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.967.0.tgz", + "integrity": "sha512-RuOan0fknnAep2pTSjmJ+Heomowxg3M3s+pcs0JEW/SYnvdwYhFOTcFg2VBvGv3V1kwXxXHMlC57zoGn6pNcqg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", - "@aws-sdk/types": "3.609.0", - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-utf8": "^3.0.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/crc64-nvme": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", - "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.965.0.tgz", + "integrity": "sha512-SfpSYqoPOAmdb3DBsnNsZ0vix+1VAtkUkzXM79JL3R5IfacpyKE2zytOgVAQx/FjhhlpSTwuXd+LRhUEVb3MaA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", - "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.965.0.tgz", + "integrity": "sha512-07T1rwAarQs33mVg5U28AsSdLB5JUXu9yBTBmspFGajKVsEahIyntf53j9mAXF1N2KR0bNdP0J4A0kst4t43UQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", - "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.965.0.tgz", + "integrity": "sha512-gjUvJRZT1bUABKewnvkj51LAynFrfz2h5DYAg5/2F4Utx6UOGByTSr9Rq8JCLbURvvzAbCtcMkkIJRxw+8Zuzw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.620.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", - "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.965.0.tgz", + "integrity": "sha512-6dvD+18Ni14KCRu+tfEoNxq1sIGVp9tvoZDZ7aMvpnA7mDXuRLrOjRQ/TAZqXwr9ENKVGyxcPl0cRK8jk1YWjA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.965.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", - "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.967.0.tgz", + "integrity": "sha512-Kkd6xGwTqbg7Spq1SI3ZX6PPYKdGLxdRGlXGNE3lnEPzNueQZQJKLZFpOY2aVdcAT+ytAY96N5szeeeAsFdUaA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.635.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-arn-parser": "3.568.0", - "@smithy/core": "^2.4.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/signature-v4": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-stream": "^3.1.3", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-arn-parser": "3.966.0", + "@smithy/core": "^3.20.2", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", - "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.965.0.tgz", + "integrity": "sha512-dke++CTw26y+a2D1DdVuZ4+2TkgItdx6TeuE0zOl4lsqXGvTBUG4eaIZalt7ZOAW5ys2pbDOk1bPuh4opoD3pQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.632.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.632.0.tgz", - "integrity": "sha512-yY/sFsHKwG9yzSf/DTclqWJaGPI2gPBJDCGBujSqTG1zlS7Ot4fqi91DZ6088BFWzbOorDzJFcAhAEFzc6LuQg==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.967.0.tgz", + "integrity": "sha512-2qzJzZj5u+cZiG7kz3XJPaTH4ssUY/aet1kwJsUTFKrWeHUf7mZZkDFfkXP5cOffgiOyR5ZkrmJoLKAde9hshg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.632.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@smithy/core": "^3.20.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.967.0.tgz", + "integrity": "sha512-PYa7V8w0gaNux6Sz/Z7zrHmPloEE+EKpRxQIOG/D0askTr5Yd4oO2KGgcInf65uHK3f0Z9U4CTUGHZvQvABypA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.967.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.967.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.2", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.3", + "@smithy/middleware-retry": "^4.4.19", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.4", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.18", + "@smithy/util-defaults-mode-node": "^4.2.21", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", - "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.965.0.tgz", + "integrity": "sha512-RoMhu9ly2B0coxn8ctXosPP2WmDD0MkQlZGLjoYHQUOCBmty5qmCxOqBmBDa6wbWbB8xKtMQ/4VXloQOgzjHXg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@aws-sdk/types": "3.965.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.635.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", - "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.967.0.tgz", + "integrity": "sha512-LfpCEqe/BliiwBtNImz/Txx6MQZkDqjP2bbk+Q4Km6mYhFU9pyPlKo3AYEHfGWn92Smt1nS3S8SzIK0nL6J2Fg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.635.0", - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/signature-v4": "^4.1.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/middleware-sdk-s3": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", - "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.967.0.tgz", + "integrity": "sha512-Qnd/nJ0CgeUa7zQczgmdQm0vYUF7pD1G0C+dR1T7huHQHRIsgCWIsCV9wNKzOFluqtcr6YAeuTwvY0+l8XWxnA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.967.0", + "@aws-sdk/nested-clients": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.614.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/types": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", - "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.965.0.tgz", + "integrity": "sha512-jvodoJdMavvg8faN7co58vVJRO5MVep4JFPRzUNCzpJ98BDqWDk/ad045aMJcmxkLzYLS2UAnUmqjJ/tUPNlzQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.568.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", - "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", + "version": "3.966.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.966.0.tgz", + "integrity": "sha512-WcCLdKBK2nHhtOPE8du5XjOXaOToxGF3Ge8rgK2jaRpjkzjS0/mO+Jp2H4+25hOne3sP2twBu5BrvD9KoXQ5LQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.632.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.632.0.tgz", - "integrity": "sha512-LlYMU8pAbcEQphOpE6xaNLJ8kPGhklZZTVzZVpVW477NaaGgoGTMYNXTABYHcxeF5E2lLrxql9OmVpvr8GWN8Q==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.965.0.tgz", + "integrity": "sha512-WqSCB0XIsGUwZWvrYkuoofi2vzoVHqyeJ2kN+WyoOsxPLTiQSBIoqm/01R/qJvoxwK/gOOF7su9i84Vw2NQQpQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", - "@smithy/util-endpoints": "^2.0.5", + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@aws-sdk/util-locate-window": { @@ -970,30 +989,33 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", - "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.965.0.tgz", + "integrity": "sha512-Xiza/zMntQGpkd2dETQeAK8So1pg5+STTzpcdGWxj5q0jGO5ayjqT/q1Q7BrsX5KIr6PvRkl9/V7lLCv04wGjQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.614.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", - "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "version": "3.967.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.967.0.tgz", + "integrity": "sha512-yUz6pCGxyG4+QaDg0dkdIBphjQp8A9rrbZa/+U3RJgRrW47hy64clFQUROzj5Poy1Ur8ICVXEUpBsSqRuYEU2g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/middleware-user-agent": "3.967.0", + "@aws-sdk/types": "3.965.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -1005,16 +1027,28 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.609.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", - "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.965.0.tgz", + "integrity": "sha512-Tcod25/BTupraQwtb+Q+GX8bmEZfxIFjjJ/AvkhUZsZlkPeVluzq1uu3Oeqf145DCdMjzLIN6vab5MrykbDP+g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", + "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" } }, "node_modules/@babel/parser": { @@ -1157,18 +1191,17 @@ } }, "node_modules/@maplibre/maplibre-gl-style-spec": { - "version": "20.3.1", - "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-20.3.1.tgz", - "integrity": "sha512-5ueL4UDitzVtceQ8J4kY+Px3WK+eZTsmGwha3MBKHKqiHvKrjWWwBCIl1K8BuJSc5OFh83uI8IFNoFvQxX2uUw==", + "version": "24.3.1", + "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-24.3.1.tgz", + "integrity": "sha512-TUM5JD40H2mgtVXl5IwWz03BuQabw8oZQLJTmPpJA0YTYF+B+oZppy5lNMO6bMvHzB+/5mxqW9VLG3wFdeqtOw==", "license": "ISC", "dependencies": { "@mapbox/jsonlint-lines-primitives": "~2.0.2", "@mapbox/unitbezier": "^0.0.1", "json-stringify-pretty-compact": "^4.0.0", "minimist": "^1.2.8", - "quickselect": "^2.0.0", + "quickselect": "^3.0.0", "rw": "^1.3.3", - "sort-object": "^3.0.3", "tinyqueue": "^3.0.0" }, "bin": { @@ -1372,693 +1405,786 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", - "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.9.tgz", + "integrity": "sha512-6YGSygFmck1vMjzSxbjEPKMm1xWUr2+w+F8kWVc8rqKQYd1C5zZftvxGii4ti4Mh5ulIXZtAUoXS88Hhu6fkjQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/chunked-blob-reader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", - "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.1.tgz", + "integrity": "sha512-y5d4xRiD6TzeP5BWlb+Ig/VFqF+t9oANNhGeMqyzU7obw7FYgTgVi50i5JqBTeKp+TABeDIeeXFZdz65RipNtA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/chunked-blob-reader-native": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", - "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.2.tgz", + "integrity": "sha512-QzzYIlf4yg0w5TQaC9VId3B3ugSk1MI/wb7tgcHtd7CBV9gNRKZrhc2EPSxSZuDy10zUZ0lomNMgkc6/VVe8xg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/util-base64": "^3.0.0", + "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/config-resolver": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", - "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.7.tgz", + "integrity": "sha512-RISbtc12JKdFRYadt2kW12Cp6XCSU00uFaBZPZqInNVSrRdJFPY/S6nd6/sV7+ySTgGPiKrERtnimEFI6sSweQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", + "@smithy/node-config-provider": "^4.3.9", + "@smithy/types": "^4.12.1", + "@smithy/util-config-provider": "^4.2.1", + "@smithy/util-endpoints": "^3.2.9", + "@smithy/util-middleware": "^4.2.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/core": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", - "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "version": "3.23.4", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.4.tgz", + "integrity": "sha512-IH7G3hWxUhd2Z6HtvjZ1EiyDBCRYRr2sngOB9KUWf96XQ8JP2O5ascUH6TouW5YCIMFaVnKADEscM/vUfI3TvA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@smithy/middleware-serde": "^4.2.10", + "@smithy/protocol-http": "^5.3.9", + "@smithy/types": "^4.12.1", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-body-length-browser": "^4.2.1", + "@smithy/util-middleware": "^4.2.9", + "@smithy/util-stream": "^4.5.14", + "@smithy/util-utf8": "^4.2.1", + "@smithy/uuid": "^1.1.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/credential-provider-imds": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", - "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.9.tgz", + "integrity": "sha512-Jf723a38EGAzWHxJHzb9DtBq7lrvdJlkCAPWQdN/oiznovx5yWXCFCVspzDe8JU6b+k9hJXYB5duFZpb+3mB6Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", + "@smithy/node-config-provider": "^4.3.9", + "@smithy/property-provider": "^4.2.9", + "@smithy/types": "^4.12.1", + "@smithy/url-parser": "^4.2.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-codec": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", - "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.9.tgz", + "integrity": "sha512-8/wOb1wm/joXCj6SNHRFnfcNBR4xmumw869UnM+RrjoWeliNcTnOTw2WZXBWoKfszbL/v/AxdijIilqRMst+vA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/types": "^4.12.1", + "@smithy/util-hex-encoding": "^4.2.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", - "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.9.tgz", + "integrity": "sha512-HbD4ptlSKHVfF84F77oqy2kswQR5H9basFILtCvnhtgzvRntiQtqstT1XFENzI7dQzrGD0HfhMjziSCs6EZEFA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.5", - "@smithy/types": "^3.3.0", + "@smithy/eventstream-serde-universal": "^4.2.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", - "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.9.tgz", + "integrity": "sha512-W2KlYzjD1V7jCUsTxy/HWrWDa9RdnzqY8Aeskaoakrj+9aiZ53YzEC7lNb3JJ0zKFjWoLbXdaSXmftBBR8Wjsw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", - "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.9.tgz", + "integrity": "sha512-6nMJG2KJJ5cjmPmySomEdpqhGsfneanKCjb5uBJJIM2D6rZhemEpYBtes6zr910LkxWseWTIbWrif0vaOB9NTA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^3.0.5", - "@smithy/types": "^3.3.0", + "@smithy/eventstream-serde-universal": "^4.2.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", - "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.9.tgz", + "integrity": "sha512-RgkumJugvbFVcifYCFeYaFpMOuLiIAcvzKe21EeaM6/KKU/4XYyf8hs/So9GSN6SDe4bqZbwB4g/rr/pIxUZmA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^3.1.2", - "@smithy/types": "^3.3.0", + "@smithy/eventstream-codec": "^4.2.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", - "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.10.tgz", + "integrity": "sha512-qF4EcrEtEf2P6f2kGGuSVe1lan26cn7PsWJBC3vZJ6D16Fm5FSN06udOMVoW6hjzQM3W7VDFwtyUG2szQY50dA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.12.1", + "@smithy/util-base64": "^4.3.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/hash-blob-browser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", - "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.10.tgz", + "integrity": "sha512-2lZvvcwTaXq6cGOcX72Ej9WU+z3T/C5NOuqIm+zLD3MlExRp9kW/Qa/p66NbBM74X0BdrdvpsMYwlkhtvHrxaQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/chunked-blob-reader": "^3.0.0", - "@smithy/chunked-blob-reader-native": "^3.0.0", - "@smithy/types": "^3.3.0", + "@smithy/chunked-blob-reader": "^5.2.1", + "@smithy/chunked-blob-reader-native": "^4.2.2", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/hash-node": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", - "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.9.tgz", + "integrity": "sha512-/iSYAwSIA/SAeLga2YEpPLLOmw3n86RW4/bkhxtY1DSTR9z5HGjbYTzPaBKv2m8a4nK1rqZWchhl41qTaqMLbg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.12.1", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/hash-stream-node": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", - "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.9.tgz", + "integrity": "sha512-WFPbY/TysowQuoWR0xOCPT3RH1KMpThUWjx75RAMLkDlTYTANzyPHZiDRslf2e5bTmCYcqCshN7up70Ic/Zqug==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.12.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/invalid-dependency": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", - "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.9.tgz", + "integrity": "sha512-J+0rlwWZKgOYugVgRE5VlVz/UFV+6cIpZkmfWBq1ld1x3htKDdHOutYhZTURIvSVztWn0T3aghCdEzGdXXsSMw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.1.tgz", + "integrity": "sha512-Yfu664Qbf1B4IYIsYgKoABt010daZjkaCRvdU/sPnZG6TtHOB0md0RjNdLGzxe5UIdn9js4ftPICzmkRa9RJ4Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/md5-js": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", - "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.9.tgz", + "integrity": "sha512-ZCCWfGj4wvqV+5OS9e/GvR5jlR7j1mMB1UkGE+V7P1USFMwcL4Z4j5mO9nGvQGkfe20KM87ymbvZIcU9tHNlIg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/types": "^4.12.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-content-length": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", - "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.9.tgz", + "integrity": "sha512-9ViCZhFkmLUDyIPeBAsW7h5/Tcix806gWqd/BBqwW6KB8mhgZTTqjRMsyTTmMo2zpF+KckpYQsSiiFrIGHRaFw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.3.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-endpoint": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", - "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "version": "4.4.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.18.tgz", + "integrity": "sha512-4OS3TP3IWZysT8KlSG/UwfKdelJmuQ2CqVNfrkjm2Rsm146/DuSTfXiD1ulgWpp9L6lJmPYfWTp7/m4b4dQSdQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-middleware": "^3.0.3", + "@smithy/core": "^3.23.4", + "@smithy/middleware-serde": "^4.2.10", + "@smithy/node-config-provider": "^4.3.9", + "@smithy/shared-ini-file-loader": "^4.4.4", + "@smithy/types": "^4.12.1", + "@smithy/url-parser": "^4.2.9", + "@smithy/util-middleware": "^4.2.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-retry": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", - "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", + "version": "4.4.35", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.35.tgz", + "integrity": "sha512-sz+Th9ofKypOtaboPTcyZtIfCs2LNb84bzxEhPffCElyMorVYDBdeGzxYqSLC6gWaZUqpPSbj5F6TIxYUlSCfQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/service-error-classification": "^3.0.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/node-config-provider": "^4.3.9", + "@smithy/protocol-http": "^5.3.9", + "@smithy/service-error-classification": "^4.2.9", + "@smithy/smithy-client": "^4.11.7", + "@smithy/types": "^4.12.1", + "@smithy/util-middleware": "^4.2.9", + "@smithy/util-retry": "^4.2.9", + "@smithy/uuid": "^1.1.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-serde": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", - "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.10.tgz", + "integrity": "sha512-BQsdoi7ma4siJAzD0S6MedNPhiMcTdTLUqEUjrHeT1TJppBKWnwqySg34Oh/uGRhJeBd1sAH2t5tghBvcyD6tw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.3.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-stack": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", - "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.9.tgz", + "integrity": "sha512-pid7ksBr7nm0X/3paIlGo9Fh3UK1pQ5yH0007tBmdkVvv+AsBZAOzC2dmLhlzDWKkSB+ZCiiyDArjAW3klkbMg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/node-config-provider": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", - "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.9.tgz", + "integrity": "sha512-EjdDTVGnnyJ9y8jXIfkF45UUZs21/Pp8xaMTZySLoC0xI3EhY7jq4co3LQnhh/bB6VVamd9ELpYJWLDw2ANhZA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/property-provider": "^4.2.9", + "@smithy/shared-ini-file-loader": "^4.4.4", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/node-http-handler": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", - "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.11.tgz", + "integrity": "sha512-kQNJFwzYA9y+Fj3h9t1ToXYOJBobwUVEc6/WX45urJXyErgG0WOsres8Se8BAiFCMe8P06OkzRgakv7bQ5S+6Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/abort-controller": "^4.2.9", + "@smithy/protocol-http": "^5.3.9", + "@smithy/querystring-builder": "^4.2.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/property-provider": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", - "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.9.tgz", + "integrity": "sha512-ibHwLxq4KlbfueoNxMNrZkG+O7V/5XKrewhDGYn0p9DYKCsdsofuWHKdX3QW4zHlAUfLStqdCUSDi/q/9WSjwA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/protocol-http": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", - "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.9.tgz", + "integrity": "sha512-PRy4yZqsKI3Eab8TLc16Dj2NzC4dnw/8E95+++Jc+wwlkjBpAq3tNLqkLHMmSvDfxKQ+X5PmmCYt+rM/GcMKPA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/querystring-builder": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", - "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.9.tgz", + "integrity": "sha512-/AIDaq0+ehv+QfeyAjCUFShwHIt+FA1IodsV/2AZE5h4PUZcQYv5sjmy9V67UWfsBoTjOPKUFYSRfGoNW9T2UQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", - "@smithy/util-uri-escape": "^3.0.0", + "@smithy/types": "^4.12.1", + "@smithy/util-uri-escape": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/querystring-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", - "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.9.tgz", + "integrity": "sha512-kZ9AHhrYTea3UoklXudEnyA4duy9KAWERC28+ft8y8HIhR3yGsjv1PFTgzMpB+5L4tQKXNTwFbVJMeRK20vpHQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/service-error-classification": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", - "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.9.tgz", + "integrity": "sha512-DYYd4xrm9Ozik+ZT4f5ZqSXdzscVHF/tFCzqieIFcLrjRDxWSgRtvtXOohJGoniLfPcBcy5ltR3tp2Lw4/d9ag==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0" + "@smithy/types": "^4.12.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", - "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.4.tgz", + "integrity": "sha512-tA5Cm11BHQCk/67y6VPIWydLh/pMY90jqOEWIr/2VAzTOoDwGpwp0C/AuHBc3/xWSOA5m5PXLN+lIOrsnTm/PQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/signature-v4": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", - "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.9.tgz", + "integrity": "sha512-QZKreDINuWf6KIcUUuurjBJiPPSRpMyU3sFPKk6urNAYcKkXhe6Ma+9MBX9e87yDnZfa/cqNMxobkdi9bpJt1A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/is-array-buffer": "^4.2.1", + "@smithy/protocol-http": "^5.3.9", + "@smithy/types": "^4.12.1", + "@smithy/util-hex-encoding": "^4.2.1", + "@smithy/util-middleware": "^4.2.9", + "@smithy/util-uri-escape": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/smithy-client": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", - "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.11.7.tgz", + "integrity": "sha512-gQP2J3qB/Wmc26gdmB8gA6zq2o2spG5sEU3o7TaTATBJEk29sYGWdEFoGEy91BczSpifTo0DQhVYjZXBEVcrpA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", + "@smithy/core": "^3.23.4", + "@smithy/middleware-endpoint": "^4.4.18", + "@smithy/middleware-stack": "^4.2.9", + "@smithy/protocol-http": "^5.3.9", + "@smithy/types": "^4.12.1", + "@smithy/util-stream": "^4.5.14", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/types": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", - "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.1.tgz", + "integrity": "sha512-ow30Ze/DD02KH2p0eMyIF2+qJzGyNb0kFrnTRtPpuOkQ4hrgvLdaU4YC6r/K8aOrCML4FH0Cmm0aI4503L1Hwg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/url-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", - "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.9.tgz", + "integrity": "sha512-gYs8FrnwKoIvL+GyPz6VvweCkrXqHeD+KnOAxB+NFy6mLr4l75lFrn3dZ413DG0K2TvFtN7L43x7r8hyyohYdg==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/querystring-parser": "^4.2.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/util-base64": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", - "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.1.tgz", + "integrity": "sha512-BKGuawX4Doq/bI/uEmg+Zyc36rJKWuin3py89PquXBIBqmbnJwBBsmKhdHfNEp0+A4TDgLmT/3MSKZ1SxHcR6w==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-body-length-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", - "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.1.tgz", + "integrity": "sha512-SiJeLiozrAoCrgDBUgsVbmqHmMgg/2bA15AzcbcW+zan7SuyAVHN4xTSbq0GlebAIwlcaX32xacnrG488/J/6g==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@smithy/util-body-length-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", - "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.2.tgz", + "integrity": "sha512-4rHqBvxtJEBvsZcFQSPQqXP2b/yy/YlB66KlcEgcH2WNoOKCKB03DSLzXmOsXjbl8dJ4OEYTn31knhdznwk7zw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.1.tgz", + "integrity": "sha512-/swhmt1qTiVkaejlmMPPDgZhEaWb/HWMGRBheaxwuVkusp/z+ErJyQxO6kaXumOciZSWlmq6Z5mNylCd33X7Ig==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/is-array-buffer": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-config-provider": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", - "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.1.tgz", + "integrity": "sha512-462id/00U8JWFw6qBuTSWfN5TxOHvDu4WliI97qOIOnuC/g+NDAknTU8eoGXEPlLkRVgWEr03jJBLV4o2FL8+A==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", - "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", + "version": "4.3.34", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.34.tgz", + "integrity": "sha512-m75CH7xaVG8ErlnfXsIBLrgVrApejrvUpohr41CMdeWNcEu/Ouvj9fbNA7oW9Qpr0Awf+BmDRrYx72hEKgY+FQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "bowser": "^2.11.0", + "@smithy/property-provider": "^4.2.9", + "@smithy/smithy-client": "^4.11.7", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", - "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", + "version": "4.2.37", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.37.tgz", + "integrity": "sha512-1LcAt0PV1dletxiGwcw2IJ8vLNhfkir02NTi1i/CFCY2ObtM5wDDjn/8V2dbPrbyoh6OTFH+uayI1rSVRBMT3A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^3.0.5", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", + "@smithy/config-resolver": "^4.4.7", + "@smithy/credential-provider-imds": "^4.2.9", + "@smithy/node-config-provider": "^4.3.9", + "@smithy/property-provider": "^4.2.9", + "@smithy/smithy-client": "^4.11.7", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">= 10.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-endpoints": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", - "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.9.tgz", + "integrity": "sha512-9FTqTzKxCFelCKdtHb22BTbrLgw7tTI+D6r/Ci/njI0tzqWLQctS0uEDTzraCR5K6IJItfFp1QmESlBytSpRhQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/node-config-provider": "^4.3.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-hex-encoding": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", - "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.1.tgz", + "integrity": "sha512-c1hHtkgAWmE35/50gmdKajgGAKV3ePJ7t6UtEmpfCWJmQE9BQAQPz0URUVI89eSkcDqCtzqllxzG28IQoZPvwA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", - "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.9.tgz", + "integrity": "sha512-pfnZneJ1S9X3TRmg2l3pG11Pvx2BW9O3NFhUN30llrK/yUKu8WbqMTx4/CzED+qKBYw0//ntUT00hvmaG+nLgA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-retry": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", - "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.9.tgz", + "integrity": "sha512-79hfhL/oxP40SCXJGfjfE9pjbUVfHhXZFpCWXTHqXSluzaVy7jwWs9Ui7lLbfDBSp+7i+BIwgeVIRerbIRWN6g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^3.0.3", - "@smithy/types": "^3.3.0", + "@smithy/service-error-classification": "^4.2.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-stream": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", - "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.14.tgz", + "integrity": "sha512-IOBEiJTOltSx6MAfwkx/GSVM8/UCJxdtw13haP5OEL543lb1DN6TAypsxv+qcj4l/rKcpapbS6zK9MQGBOhoaA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-buffer-from": "^3.0.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/fetch-http-handler": "^5.3.10", + "@smithy/node-http-handler": "^4.4.11", + "@smithy/types": "^4.12.1", + "@smithy/util-base64": "^4.3.1", + "@smithy/util-buffer-from": "^4.2.1", + "@smithy/util-hex-encoding": "^4.2.1", + "@smithy/util-utf8": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-uri-escape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", - "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.1.tgz", + "integrity": "sha512-YmiUDn2eo2IOiWYYvGQkgX5ZkBSiTQu4FlDo5jNPpAxng2t6Sjb6WutnZV9l6VR4eJul1ABmCrnWBC9hKHQa6Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.1.tgz", + "integrity": "sha512-DSIwNaWtmzrNQHv8g7DBGR9mulSit65KSj5ymGEIAknmIN8IpbZefEep10LaMG/P/xquwbmJ1h9ectz8z6mV6g==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-buffer-from": "^4.2.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@smithy/util-waiter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", - "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.9.tgz", + "integrity": "sha512-/PYREwfBaj3fV5V4PfMksYj/WKwrjQ4gW/yo8KLpZSkAdBEkvXd68hovAubrw+n+Q8Rcr9XRn6uzcoQCEhrNFQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.1", - "@smithy/types": "^3.3.0", + "@smithy/abort-controller": "^4.2.9", + "@smithy/types": "^4.12.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.1.tgz", + "integrity": "sha512-dSfDCeihDmZlV2oyr0yWPTUfh07suS+R5OB+FZGiv/hHyK3hrFBW5rR1UYjfa57vBsrP9lciFkRPzebaV1Qujw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@types/ejs": { @@ -2091,30 +2217,6 @@ "node": ">= 0.6" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", @@ -2157,20 +2259,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -2199,44 +2293,87 @@ "license": "Apache-2.0" }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/bytes": { @@ -2249,25 +2386,6 @@ "node": ">= 0.8" } }, - "node_modules/bytewise": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", - "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==", - "license": "MIT", - "dependencies": { - "bytewise-core": "^1.2.2", - "typewise": "^1.0.3" - } - }, - "node_modules/bytewise-core": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", - "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==", - "license": "MIT", - "dependencies": { - "typewise-core": "^1.2" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -2286,40 +2404,35 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2518,6 +2631,20 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2618,12 +2745,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -2637,9 +2762,10 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -2702,40 +2828,40 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -2748,59 +2874,36 @@ "url": "https://opencollective.com/express" } }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-xml-parser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", - "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" } ], + "license": "MIT", "dependencies": { - "strnum": "^1.0.5" + "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.5.tgz", + "integrity": "sha512-ct/ckWBV/9Dg3MlvCXsLcSUyoWwv9mCKqlhLNB2DAuXR/NZolSXlQqP5dyy6guWlPXBhodZyZ5lGPQcbQDxrEQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" + "minimatch": "^10.2.1" }, "engines": { - "node": ">=10" + "node": "20 || >=22" } }, "node_modules/finalhandler": { @@ -2883,15 +2986,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2900,6 +3009,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -2916,15 +3038,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/glob": { "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", @@ -2953,10 +3066,11 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2979,11 +3093,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3002,15 +3117,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -3034,9 +3140,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3244,15 +3351,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -3278,18 +3376,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -3377,25 +3463,16 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", + "async": "^3.2.6", "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" @@ -3404,29 +3481,6 @@ "node": ">=10" } }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jake/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -3452,10 +3506,11 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" }, "node_modules/lodash._reinterpolate": { "version": "3.0.0", @@ -3482,6 +3537,15 @@ "lodash._reinterpolate": "^3.0.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3568,27 +3632,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", - "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", - "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -3726,9 +3769,10 @@ } }, "node_modules/npm-run-all/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3748,9 +3792,13 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3877,6 +3925,13 @@ "pbf": "bin/pbf" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/pidtree": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", @@ -3933,13 +3988,13 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -3949,9 +4004,9 @@ } }, "node_modules/quickselect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", - "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", "license": "ISC" }, "node_modules/range-parser": { @@ -3965,21 +4020,52 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -4194,21 +4280,6 @@ "node": ">= 0.4" } }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -4244,14 +4315,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -4298,41 +4424,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT" }, - "node_modules/sort-asc": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz", - "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sort-desc": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz", - "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sort-object": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz", - "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==", - "license": "MIT", - "dependencies": { - "bytewise": "^1.1.0", - "get-value": "^2.0.2", - "is-extendable": "^0.1.1", - "sort-asc": "^0.2.0", - "sort-desc": "^0.2.0", - "union-value": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -4361,43 +4452,6 @@ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "license": "MIT", - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split-string/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -4480,22 +4534,17 @@ } }, "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", - "dev": true - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", @@ -4636,21 +4685,6 @@ "node": ">=14.17" } }, - "node_modules/typewise": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", - "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==", - "license": "MIT", - "dependencies": { - "typewise-core": "^1.2.0" - } - }, - "node_modules/typewise-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", - "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==", - "license": "MIT" - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -4684,21 +4718,6 @@ "dev": true, "license": "MIT" }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "license": "MIT", - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/universal-user-agent": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", @@ -4725,19 +4744,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 952a8d608..61bd43b53 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "license": "BSD-2-Clause", "dependencies": { "@actions/core": "^1.11.1", - "@maplibre/maplibre-gl-style-spec": "^20.3.1", + "@maplibre/maplibre-gl-style-spec": "^24.3.1", "minimatch": "^10.2.2", "npm-run-all": "^4.1.5", "simple-git": "^3.25.0" diff --git a/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt b/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt index 8a1d934ba..bc31ea6ea 100644 --- a/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt +++ b/platform/android/MapLibreAndroid/src/cpp/CMakeLists.txt @@ -147,6 +147,8 @@ add_library(maplibre SHARED ${PROJECT_SOURCE_DIR}/style/layers/background_layer.hpp ${PROJECT_SOURCE_DIR}/style/layers/circle_layer.cpp ${PROJECT_SOURCE_DIR}/style/layers/circle_layer.hpp + ${PROJECT_SOURCE_DIR}/style/layers/color_relief_layer.cpp + ${PROJECT_SOURCE_DIR}/style/layers/color_relief_layer.hpp ${PROJECT_SOURCE_DIR}/style/layers/custom_layer.cpp ${PROJECT_SOURCE_DIR}/style/layers/custom_layer.hpp ${PROJECT_SOURCE_DIR}/style/layers/fill_extrusion_layer.cpp diff --git a/platform/android/MapLibreAndroid/src/cpp/conversion/constant.cpp b/platform/android/MapLibreAndroid/src/cpp/conversion/constant.cpp index 44321e488..02df39ab3 100644 --- a/platform/android/MapLibreAndroid/src/cpp/conversion/constant.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/conversion/constant.cpp @@ -101,6 +101,17 @@ Result>> Converter>, std::vect return result; } +Result>> Converter>, std::vector>::operator()( + jni::JNIEnv& env, const std::vector& value) const { + auto result = jni::Array::New(env, value.size()); + + for (std::size_t i = 0; i < value.size(); i++) { + result.Set(env, i, jni::Make(env, value.at(i).stringify())); + } + + return result; +} + Result>> Converter>, style::expression::Image>::operator()( jni::JNIEnv& env, const style::expression::Image& value) const { return jni::Make(env, value.id()); diff --git a/platform/android/MapLibreAndroid/src/cpp/conversion/constant.hpp b/platform/android/MapLibreAndroid/src/cpp/conversion/constant.hpp index 661a58ce1..eb474f857 100644 --- a/platform/android/MapLibreAndroid/src/cpp/conversion/constant.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/conversion/constant.hpp @@ -111,6 +111,11 @@ struct Converter>, std::vector> { Result>> operator()(jni::JNIEnv& env, const std::vector& value) const; }; +template <> +struct Converter>, std::vector> { + Result>> operator()(jni::JNIEnv& env, const std::vector& value) const; +}; + template struct Converter>, T, typename std::enable_if_t::value>> { Result>> operator()(jni::JNIEnv& env, const T& value) const { diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/color_relief_layer.cpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/color_relief_layer.cpp new file mode 100644 index 000000000..b245f59ea --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/color_relief_layer.cpp @@ -0,0 +1,130 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include "color_relief_layer.hpp" + +#include + +#include "../conversion/property_value.hpp" +#include "../conversion/transition_options.hpp" + +#include + +namespace mbgl { +namespace android { + +inline mbgl::style::ColorReliefLayer& toColorReliefLayer(mbgl::style::Layer& layer) { + return static_cast(layer); +} + +/** + * Creates an owning peer object (for layers not attached to the map) from the JVM side + */ +ColorReliefLayer::ColorReliefLayer(jni::JNIEnv& env, jni::String& layerId, jni::String& sourceId) + : Layer(std::make_unique(jni::Make(env, layerId), + jni::Make(env, sourceId))) {} + +/** + * Creates a non-owning peer object (for layers currently attached to the map) + */ +ColorReliefLayer::ColorReliefLayer(mbgl::style::ColorReliefLayer& coreLayer) + : Layer(coreLayer) {} + +/** + * Creates an owning peer object (for layers not attached to the map) + */ +ColorReliefLayer::ColorReliefLayer(std::unique_ptr coreLayer) + : Layer(std::move(coreLayer)) {} + +ColorReliefLayer::~ColorReliefLayer() = default; + +// Property getters + +jni::Local> ColorReliefLayer::getColorReliefOpacity(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + auto layer = layerPtr.get(); + if (!layer) { + return std::move( + *convert>>(env, style::ColorReliefLayer::getDefaultColorReliefOpacity())); + } + return std::move(*convert>>(env, toColorReliefLayer(*layer).getColorReliefOpacity())); +} + +jni::Local> ColorReliefLayer::getColorReliefOpacityTransition(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + auto layer = layerPtr.get(); + if (!layer) { + return std::move(*convert>>(env, mbgl::style::TransitionOptions())); + } + mbgl::style::TransitionOptions options = toColorReliefLayer(*layer).getColorReliefOpacityTransition(); + return std::move(*convert>>(env, options)); +} + +void ColorReliefLayer::setColorReliefOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay) { + auto layer = layerPtr.get(); + if (!layer) { + return; + } + mbgl::style::TransitionOptions options; + options.duration.emplace(mbgl::Milliseconds(duration)); + options.delay.emplace(mbgl::Milliseconds(delay)); + toColorReliefLayer(*layer).setColorReliefOpacityTransition(options); +} + +jni::Local> ColorReliefLayer::getColorReliefColor(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + auto layer = layerPtr.get(); + if (!layer) { + return std::move( + *convert>>(env, style::ColorReliefLayer::getDefaultColorReliefColor())); + } + return std::move(*convert>>(env, toColorReliefLayer(*layer).getColorReliefColor())); +} + +// ColorReliefJavaLayerPeerFactory + +ColorReliefJavaLayerPeerFactory::~ColorReliefJavaLayerPeerFactory() = default; + +namespace { +jni::Local> createJavaPeer(jni::JNIEnv& env, Layer* layer) { + static auto& javaClass = jni::Class::Singleton(env); + static auto constructor = javaClass.GetConstructor(env); + return javaClass.New(env, constructor, reinterpret_cast(layer)); +} +} // namespace + +jni::Local> ColorReliefJavaLayerPeerFactory::createJavaLayerPeer(jni::JNIEnv& env, + mbgl::style::Layer& layer) { + assert(layer.baseImpl->getTypeInfo() == getTypeInfo()); + return createJavaPeer(env, new ColorReliefLayer(toColorReliefLayer(layer))); +} + +jni::Local> ColorReliefJavaLayerPeerFactory::createJavaLayerPeer( + jni::JNIEnv& env, std::unique_ptr layer) { + assert(layer->baseImpl->getTypeInfo() == getTypeInfo()); + return createJavaPeer(env, + new ColorReliefLayer(std::unique_ptr( + static_cast(layer.release())))); +} + +void ColorReliefJavaLayerPeerFactory::registerNative(jni::JNIEnv& env) { + // Lookup the class + static auto& javaClass = jni::Class::Singleton(env); + +#define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + // Register the peer + jni::RegisterNativePeer( + env, + javaClass, + "nativePtr", + jni::MakePeer, + "initialize", + "finalize", + METHOD(&ColorReliefLayer::getColorReliefOpacityTransition, "nativeGetColorReliefOpacityTransition"), + METHOD(&ColorReliefLayer::setColorReliefOpacityTransition, "nativeSetColorReliefOpacityTransition"), + METHOD(&ColorReliefLayer::getColorReliefOpacity, "nativeGetColorReliefOpacity"), + METHOD(&ColorReliefLayer::getColorReliefColor, "nativeGetColorReliefColor")); +} + +} // namespace android +} // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/color_relief_layer.hpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/color_relief_layer.hpp new file mode 100644 index 000000000..8c4279ea7 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/color_relief_layer.hpp @@ -0,0 +1,52 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include "layer.hpp" +#include "../transition_options.hpp" +#include +#include +#include + +namespace mbgl { +namespace android { + +class ColorReliefLayer : public Layer { +public: + using SuperTag = Layer; + static constexpr auto Name() { return "org/maplibre/android/style/layers/ColorReliefLayer"; }; + + ColorReliefLayer(jni::JNIEnv&, jni::String&, jni::String&); + + ColorReliefLayer(mbgl::style::ColorReliefLayer&); + + ColorReliefLayer(std::unique_ptr); + + ~ColorReliefLayer(); + + // Properties + + jni::Local> getColorReliefOpacity(jni::JNIEnv&); + void setColorReliefOpacityTransition(jni::JNIEnv&, jlong duration, jlong delay); + jni::Local> getColorReliefOpacityTransition(jni::JNIEnv&); + + jni::Local> getColorReliefColor(jni::JNIEnv&); + +}; // class ColorReliefLayer + +class ColorReliefJavaLayerPeerFactory final : public JavaLayerPeerFactory, public mbgl::ColorReliefLayerFactory { +public: + ~ColorReliefJavaLayerPeerFactory() override; + + // JavaLayerPeerFactory overrides. + jni::Local> createJavaLayerPeer(jni::JNIEnv&, mbgl::style::Layer&) final; + jni::Local> createJavaLayerPeer(jni::JNIEnv& env, std::unique_ptr) final; + + void registerNative(jni::JNIEnv&) final; + + LayerFactory* getLayerFactory() final { return this; } + +}; // class ColorReliefJavaLayerPeerFactory + +} // namespace android +} // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.cpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.cpp index 271a7765a..bb354ad8e 100644 --- a/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.cpp @@ -50,6 +50,17 @@ jni::Local> HillshadeLayer::getHillshadeIlluminationDirection(jni: *convert>>(env, toHillshadeLayer(*layer).getHillshadeIlluminationDirection())); } +jni::Local> HillshadeLayer::getHillshadeIlluminationAltitude(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + auto layer = layerPtr.get(); + if (!layer) { + return std::move( + *convert>>(env, style::HillshadeLayer::getDefaultHillshadeIlluminationAltitude())); + } + return std::move( + *convert>>(env, toHillshadeLayer(*layer).getHillshadeIlluminationAltitude())); +} + jni::Local> HillshadeLayer::getHillshadeIlluminationAnchor(jni::JNIEnv& env) { using namespace mbgl::android::conversion; auto layer = layerPtr.get(); @@ -185,6 +196,15 @@ void HillshadeLayer::setHillshadeAccentColorTransition(jni::JNIEnv&, jlong durat toHillshadeLayer(*layer).setHillshadeAccentColorTransition(options); } +jni::Local> HillshadeLayer::getHillshadeMethod(jni::JNIEnv& env) { + using namespace mbgl::android::conversion; + auto layer = layerPtr.get(); + if (!layer) { + return std::move(*convert>>(env, style::HillshadeLayer::getDefaultHillshadeMethod())); + } + return std::move(*convert>>(env, toHillshadeLayer(*layer).getHillshadeMethod())); +} + // HillshadeJavaLayerPeerFactory HillshadeJavaLayerPeerFactory::~HillshadeJavaLayerPeerFactory() = default; @@ -226,6 +246,7 @@ void HillshadeJavaLayerPeerFactory::registerNative(jni::JNIEnv& env) { "initialize", "finalize", METHOD(&HillshadeLayer::getHillshadeIlluminationDirection, "nativeGetHillshadeIlluminationDirection"), + METHOD(&HillshadeLayer::getHillshadeIlluminationAltitude, "nativeGetHillshadeIlluminationAltitude"), METHOD(&HillshadeLayer::getHillshadeIlluminationAnchor, "nativeGetHillshadeIlluminationAnchor"), METHOD(&HillshadeLayer::getHillshadeExaggerationTransition, "nativeGetHillshadeExaggerationTransition"), METHOD(&HillshadeLayer::setHillshadeExaggerationTransition, "nativeSetHillshadeExaggerationTransition"), @@ -238,7 +259,8 @@ void HillshadeJavaLayerPeerFactory::registerNative(jni::JNIEnv& env) { METHOD(&HillshadeLayer::getHillshadeHighlightColor, "nativeGetHillshadeHighlightColor"), METHOD(&HillshadeLayer::getHillshadeAccentColorTransition, "nativeGetHillshadeAccentColorTransition"), METHOD(&HillshadeLayer::setHillshadeAccentColorTransition, "nativeSetHillshadeAccentColorTransition"), - METHOD(&HillshadeLayer::getHillshadeAccentColor, "nativeGetHillshadeAccentColor")); + METHOD(&HillshadeLayer::getHillshadeAccentColor, "nativeGetHillshadeAccentColor"), + METHOD(&HillshadeLayer::getHillshadeMethod, "nativeGetHillshadeMethod")); } } // namespace android diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.hpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.hpp index 50559a59a..354d7ffa4 100644 --- a/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/hillshade_layer.hpp @@ -28,6 +28,8 @@ public: jni::Local> getHillshadeIlluminationDirection(jni::JNIEnv&); + jni::Local> getHillshadeIlluminationAltitude(jni::JNIEnv&); + jni::Local> getHillshadeIlluminationAnchor(jni::JNIEnv&); jni::Local> getHillshadeExaggeration(jni::JNIEnv&); @@ -46,6 +48,8 @@ public: void setHillshadeAccentColorTransition(jni::JNIEnv&, jlong duration, jlong delay); jni::Local> getHillshadeAccentColorTransition(jni::JNIEnv&); + jni::Local> getHillshadeMethod(jni::JNIEnv&); + }; // class HillshadeLayer class HillshadeJavaLayerPeerFactory final : public JavaLayerPeerFactory, public mbgl::HillshadeLayerFactory { diff --git a/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp b/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp index e53c46044..85f39dc11 100644 --- a/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/style/layers/layer_manager.cpp @@ -8,6 +8,7 @@ #include "background_layer.hpp" #include "circle_layer.hpp" +#include "color_relief_layer.hpp" #include "custom_layer.hpp" #include "fill_extrusion_layer.hpp" #include "fill_layer.hpp" @@ -59,6 +60,11 @@ LayerManagerAndroid::LayerManagerAndroid() { #elif !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL) addLayerType(std::make_unique()); #endif +#if defined(MBGL_LAYER_COLOR_RELIEF_DISABLE_RUNTIME) + addLayerTypeCoreOnly(std::make_unique()); +#elif !defined(MBGL_LAYER_COLOR_RELIEF_DISABLE_ALL) + addLayerType(std::make_unique()); +#endif #if defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_RUNTIME) addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL) diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/ColorReliefLayer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/ColorReliefLayer.java new file mode 100644 index 000000000..908a21e1b --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/ColorReliefLayer.java @@ -0,0 +1,227 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +package org.maplibre.android.style.layers; + +import androidx.annotation.ColorInt; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; + +import static org.maplibre.android.utils.ColorUtils.rgbaToColor; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import org.maplibre.android.style.expressions.Expression; +import org.maplibre.android.style.layers.TransitionOptions; + +/** + * Client-side elevation coloring based on DEM data. The implementation supports Mapbox Terrain RGB, Mapzen Terrarium tiles and custom encodings. + * + * @see The online documentation + */ +@UiThread +public class ColorReliefLayer extends Layer { + + /** + * Creates a ColorReliefLayer. + * + * @param nativePtr pointer used by core + */ + @Keep + ColorReliefLayer(long nativePtr) { + super(nativePtr); + } + + /** + * Creates a ColorReliefLayer. + * + * @param layerId the id of the layer + * @param sourceId the id of the source + */ + public ColorReliefLayer(String layerId, String sourceId) { + super(); + initialize(layerId, sourceId); + } + + @Keep + protected native void initialize(String layerId, String sourceId); + + /** + * Set the source layer. + * + * @param sourceLayer the source layer to set + */ + public void setSourceLayer(String sourceLayer) { + checkThread(); + nativeSetSourceLayer(sourceLayer); + } + + /** + * Set the source Layer. + * + * @param sourceLayer the source layer to set + * @return This + */ + @NonNull + public ColorReliefLayer withSourceLayer(String sourceLayer) { + setSourceLayer(sourceLayer); + return this; + } + + /** + * Get the source id. + * + * @return id of the source + */ + @NonNull + public String getSourceId() { + checkThread(); + return nativeGetSourceId(); + } + + /** + * Get the source layer. + * + * @return sourceLayer the source layer to get + */ + @NonNull + public String getSourceLayer() { + checkThread(); + return nativeGetSourceLayer(); + } + + /** + * Set a single expression filter. + * + * @param filter the expression filter to set + */ + public void setFilter(@NonNull Expression filter) { + checkThread(); + nativeSetFilter(filter.toArray()); + } + + /** + * Set a single expression filter. + * + * @param filter the expression filter to set + * @return This + */ + @NonNull + public ColorReliefLayer withFilter(@NonNull Expression filter) { + setFilter(filter); + return this; + } + + /** + * Get a single expression filter. + * + * @return the expression filter to get + */ + @Nullable + public Expression getFilter() { + checkThread(); + JsonElement jsonElement = nativeGetFilter(); + if (jsonElement != null) { + return Expression.Converter.convert(jsonElement); + } else { + return null; + } + } + + /** + * Set a property or properties. + * + * @param properties the var-args properties + * @return This + */ + @NonNull + public ColorReliefLayer withProperties(@NonNull PropertyValue... properties) { + setProperties(properties); + return this; + } + + // Property getters + + /** + * Get the ColorReliefOpacity property + * + * @return property wrapper value around Float + */ + @NonNull + @SuppressWarnings("unchecked") + public PropertyValue getColorReliefOpacity() { + checkThread(); + return (PropertyValue) new PropertyValue("color-relief-opacity", nativeGetColorReliefOpacity()); + } + + /** + * Get the ColorReliefOpacity property transition options + * + * @return transition options for Float + */ + @NonNull + public TransitionOptions getColorReliefOpacityTransition() { + checkThread(); + return nativeGetColorReliefOpacityTransition(); + } + + /** + * Set the ColorReliefOpacity property transition options + * + * @param options transition options for Float + */ + public void setColorReliefOpacityTransition(@NonNull TransitionOptions options) { + checkThread(); + nativeSetColorReliefOpacityTransition(options.getDuration(), options.getDelay()); + } + + /** + * Get the ColorReliefColor property + * + * @return property wrapper value around String + */ + @NonNull + @SuppressWarnings("unchecked") + public PropertyValue getColorReliefColor() { + checkThread(); + return (PropertyValue) new PropertyValue("color-relief-color", nativeGetColorReliefColor()); + } + + /** + * Defines the color of each pixel based on its elevation. Should be an expression that uses `["elevation"]` as input. + * + * @return int representation of a rgba string color + * @throws RuntimeException thrown if property isn't a value + */ + @ColorInt + public int getColorReliefColorAsInt() { + checkThread(); + PropertyValue value = getColorReliefColor(); + if (value.isValue()) { + return rgbaToColor(value.getValue()); + } else { + throw new RuntimeException("color-relief-color was set as a Function"); + } + } + + @NonNull + @Keep + private native Object nativeGetColorReliefOpacity(); + + @NonNull + @Keep + private native TransitionOptions nativeGetColorReliefOpacityTransition(); + + @Keep + private native void nativeSetColorReliefOpacityTransition(long duration, long delay); + + @NonNull + @Keep + private native Object nativeGetColorReliefColor(); + + @Override + @Keep + protected native void finalize() throws Throwable; + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/HillshadeLayer.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/HillshadeLayer.java index 16f1645f4..20a6b1ef3 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/HillshadeLayer.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/HillshadeLayer.java @@ -97,13 +97,25 @@ public class HillshadeLayer extends Layer { /** * Get the HillshadeIlluminationDirection property * - * @return property wrapper value around Float + * @return property wrapper value around Float[] */ @NonNull @SuppressWarnings("unchecked") - public PropertyValue getHillshadeIlluminationDirection() { + public PropertyValue getHillshadeIlluminationDirection() { checkThread(); - return (PropertyValue) new PropertyValue("hillshade-illumination-direction", nativeGetHillshadeIlluminationDirection()); + return (PropertyValue) new PropertyValue("hillshade-illumination-direction", nativeGetHillshadeIlluminationDirection()); + } + + /** + * Get the HillshadeIlluminationAltitude property + * + * @return property wrapper value around Float[] + */ + @NonNull + @SuppressWarnings("unchecked") + public PropertyValue getHillshadeIlluminationAltitude() { + checkThread(); + return (PropertyValue) new PropertyValue("hillshade-illumination-altitude", nativeGetHillshadeIlluminationAltitude()); } /** @@ -154,36 +166,19 @@ public class HillshadeLayer extends Layer { /** * Get the HillshadeShadowColor property * - * @return property wrapper value around String + * @return property wrapper value around String[] */ @NonNull @SuppressWarnings("unchecked") - public PropertyValue getHillshadeShadowColor() { + public PropertyValue getHillshadeShadowColor() { checkThread(); - return (PropertyValue) 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() { - checkThread(); - PropertyValue value = getHillshadeShadowColor(); - if (value.isValue()) { - return rgbaToColor(value.getValue()); - } else { - throw new RuntimeException("hillshade-shadow-color was set as a Function"); - } + return (PropertyValue) new PropertyValue("hillshade-shadow-color", nativeGetHillshadeShadowColor()); } /** * Get the HillshadeShadowColor property transition options * - * @return transition options for String + * @return transition options for String[] */ @NonNull public TransitionOptions getHillshadeShadowColorTransition() { @@ -194,7 +189,7 @@ public class HillshadeLayer extends Layer { /** * Set the HillshadeShadowColor property transition options * - * @param options transition options for String + * @param options transition options for String[] */ public void setHillshadeShadowColorTransition(@NonNull TransitionOptions options) { checkThread(); @@ -204,36 +199,19 @@ public class HillshadeLayer extends Layer { /** * Get the HillshadeHighlightColor property * - * @return property wrapper value around String + * @return property wrapper value around String[] */ @NonNull @SuppressWarnings("unchecked") - public PropertyValue getHillshadeHighlightColor() { + public PropertyValue getHillshadeHighlightColor() { checkThread(); - return (PropertyValue) 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() { - checkThread(); - PropertyValue value = getHillshadeHighlightColor(); - if (value.isValue()) { - return rgbaToColor(value.getValue()); - } else { - throw new RuntimeException("hillshade-highlight-color was set as a Function"); - } + return (PropertyValue) new PropertyValue("hillshade-highlight-color", nativeGetHillshadeHighlightColor()); } /** * Get the HillshadeHighlightColor property transition options * - * @return transition options for String + * @return transition options for String[] */ @NonNull public TransitionOptions getHillshadeHighlightColorTransition() { @@ -244,7 +222,7 @@ public class HillshadeLayer extends Layer { /** * Set the HillshadeHighlightColor property transition options * - * @param options transition options for String + * @param options transition options for String[] */ public void setHillshadeHighlightColorTransition(@NonNull TransitionOptions options) { checkThread(); @@ -301,10 +279,26 @@ public class HillshadeLayer extends Layer { nativeSetHillshadeAccentColorTransition(options.getDuration(), options.getDelay()); } + /** + * Get the HillshadeMethod property + * + * @return property wrapper value around String + */ + @NonNull + @SuppressWarnings("unchecked") + public PropertyValue getHillshadeMethod() { + checkThread(); + return (PropertyValue) new PropertyValue("hillshade-method", nativeGetHillshadeMethod()); + } + @NonNull @Keep private native Object nativeGetHillshadeIlluminationDirection(); + @NonNull + @Keep + private native Object nativeGetHillshadeIlluminationAltitude(); + @NonNull @Keep private native Object nativeGetHillshadeIlluminationAnchor(); @@ -353,6 +347,10 @@ public class HillshadeLayer extends Layer { @Keep private native void nativeSetHillshadeAccentColorTransition(long duration, long delay); + @NonNull + @Keep + private native Object nativeGetHillshadeMethod(); + @Override @Keep protected native void finalize() throws Throwable; diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/Property.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/Property.java index f2a6d16be..2172c16e5 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/Property.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/Property.java @@ -648,6 +648,42 @@ public final class Property { @Retention(RetentionPolicy.SOURCE) public @interface HILLSHADE_ILLUMINATION_ANCHOR {} + // HILLSHADE_METHOD: The hillshade algorithm to use, one of `standard`, `basic`, `combined`, `igor`, or `multidirectional`. ![image](assets/hillshade_methods.png) + + /** + * The legacy hillshade method. + */ + public static final String HILLSHADE_METHOD_STANDARD = "standard"; + /** + * Basic hillshade. Uses a simple physics model where the reflected light intensity is proportional to the cosine of the angle between the incident light and the surface normal. Similar to GDAL's {@link GDALDEM} default algorithm. + */ + public static final String HILLSHADE_METHOD_BASIC = "basic"; + /** + * Hillshade algorithm whose intensity scales with slope. Similar to GDAL's {@link GDALDEM} with -combined option. + */ + public static final String HILLSHADE_METHOD_COMBINED = "combined"; + /** + * Hillshade algorithm which tries to minimize effects on other map features beneath. Similar to GDAL's {@link GDALDEM} with -igor option. + */ + public static final String HILLSHADE_METHOD_IGOR = "igor"; + /** + * Hillshade with multiple illumination directions. Uses the basic hillshade model with multiple independent light sources. + */ + public static final String HILLSHADE_METHOD_MULTIDIRECTIONAL = "multidirectional"; + + /** + * The hillshade algorithm to use, one of `standard`, `basic`, `combined`, `igor`, or `multidirectional`. ![image](assets/hillshade_methods.png) + */ + @StringDef({ + HILLSHADE_METHOD_STANDARD, + HILLSHADE_METHOD_BASIC, + HILLSHADE_METHOD_COMBINED, + HILLSHADE_METHOD_IGOR, + HILLSHADE_METHOD_MULTIDIRECTIONAL, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HILLSHADE_METHOD {} + // ANCHOR: Whether extruded geometries are lit relative to the map or viewport. /** diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java index 2613a2dc8..82e810313 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/style/layers/PropertyFactory.java @@ -507,6 +507,7 @@ public class PropertyFactory { /** * Distance of halo to the icon outline. + The unit is in density-independent pixels only for SDF sprites that were created with a blur radius of 8, multiplied by the display density. I.e., the radius needs to be 16 for `@2x` sprites, etc. * * @param value a Float value @@ -518,6 +519,7 @@ The unit is in density-independent pixels only for SDF sprites that were created /** * Distance of halo to the icon outline. + The unit is in density-independent pixels only for SDF sprites that were created with a blur radius of 8, multiplied by the display density. I.e., the radius needs to be 16 for `@2x` sprites, etc. * * @param expression an expression statement @@ -1428,17 +1430,17 @@ The unit is in density-independent pixels only for SDF sprites that were created } /** - * 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`. + * The direction of the light source(s) 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`. Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. * - * @param value a Float value - * @return property wrapper around Float + * @param value a Float[] value + * @return property wrapper around Float[] */ - public static PropertyValue hillshadeIlluminationDirection(Float value) { + public static PropertyValue 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`. + * The direction of the light source(s) 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`. Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. * * @param expression an expression statement * @return property wrapper around an expression statement @@ -1447,6 +1449,26 @@ The unit is in density-independent pixels only for SDF sprites that were created return new PaintPropertyValue<>("hillshade-illumination-direction", expression); } + /** + * The altitude of the light source(s) used to generate the hillshading with 0 as sunset and 90 as noon. Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. + * + * @param value a Float[] value + * @return property wrapper around Float[] + */ + public static PropertyValue hillshadeIlluminationAltitude(Float[] value) { + return new PaintPropertyValue<>("hillshade-illumination-altitude", value); + } + + /** + * The altitude of the light source(s) used to generate the hillshading with 0 as sunset and 90 as noon. Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue hillshadeIlluminationAltitude(Expression expression) { + return new PaintPropertyValue<>("hillshade-illumination-altitude", expression); + } + /** * Direction of light source when map is rotated. * @@ -1488,27 +1510,17 @@ The unit is in density-independent pixels only for SDF sprites that were created } /** - * The shading color of areas that face away from the light source. + * The shading color of areas that face away from the light source(s). Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. * - * @param value a int color value - * @return property wrapper around String color + * @param value a String[] value + * @return property wrapper around String[] */ - public static PropertyValue 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 hillshadeShadowColor(String value) { + public static PropertyValue hillshadeShadowColor(String[] value) { return new PaintPropertyValue<>("hillshade-shadow-color", value); } /** - * The shading color of areas that face away from the light source. + * The shading color of areas that face away from the light source(s). Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. * * @param expression an expression statement * @return property wrapper around an expression statement @@ -1518,27 +1530,17 @@ The unit is in density-independent pixels only for SDF sprites that were created } /** - * The shading color of areas that faces towards the light source. + * The shading color of areas that faces towards the light source(s). Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. * - * @param value a int color value - * @return property wrapper around String color + * @param value a String[] value + * @return property wrapper around String[] */ - public static PropertyValue 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 hillshadeHighlightColor(String value) { + public static PropertyValue hillshadeHighlightColor(String[] value) { return new PaintPropertyValue<>("hillshade-highlight-color", value); } /** - * The shading color of areas that faces towards the light source. + * The shading color of areas that faces towards the light source(s). Only when {@link Property.HILLSHADE_METHOD} is set to `multidirectional` can you specify multiple light sources. * * @param expression an expression statement * @return property wrapper around an expression statement @@ -1577,6 +1579,76 @@ The unit is in density-independent pixels only for SDF sprites that were created return new PaintPropertyValue<>("hillshade-accent-color", expression); } + /** + * The hillshade algorithm to use, one of `standard`, `basic`, `combined`, `igor`, or `multidirectional`. ![image](assets/hillshade_methods.png) + * + * @param value a String value + * @return property wrapper around String + */ + public static PropertyValue hillshadeMethod(@Property.HILLSHADE_METHOD String value) { + return new PaintPropertyValue<>("hillshade-method", value); + } + + /** + * The hillshade algorithm to use, one of `standard`, `basic`, `combined`, `igor`, or `multidirectional`. ![image](assets/hillshade_methods.png) + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue hillshadeMethod(Expression expression) { + return new PaintPropertyValue<>("hillshade-method", expression); + } + + /** + * The opacity at which the color-relief will be drawn. + * + * @param value a Float value + * @return property wrapper around Float + */ + public static PropertyValue colorReliefOpacity(Float value) { + return new PaintPropertyValue<>("color-relief-opacity", value); + } + + /** + * The opacity at which the color-relief will be drawn. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue colorReliefOpacity(Expression expression) { + return new PaintPropertyValue<>("color-relief-opacity", expression); + } + + /** + * Defines the color of each pixel based on its elevation. Should be an expression that uses `["elevation"]` as input. + * + * @param value a int color value + * @return property wrapper around String color + */ + public static PropertyValue colorReliefColor(@ColorInt int value) { + return new PaintPropertyValue<>("color-relief-color", colorToRgbaString(value)); + } + + /** + * Defines the color of each pixel based on its elevation. Should be an expression that uses `["elevation"]` as input. + * + * @param value a String value + * @return property wrapper around String + */ + public static PropertyValue colorReliefColor(String value) { + return new PaintPropertyValue<>("color-relief-color", value); + } + + /** + * Defines the color of each pixel based on its elevation. Should be an expression that uses `["elevation"]` as input. + * + * @param expression an expression statement + * @return property wrapper around an expression statement + */ + public static PropertyValue colorReliefColor(Expression expression) { + return new PaintPropertyValue<>("color-relief-color", expression); + } + /** * The color with which the background will be drawn. * @@ -2228,7 +2300,7 @@ The unit is in density-independent pixels only for SDF sprites that were created } /** - * Font stack to use for displaying text. + * Fonts to use for displaying text. If the `glyphs` root property is specified, this array is joined together and interpreted as a font stack name. Otherwise, it is interpreted as a cascading fallback list of local font names. * * @param value a String[] value * @return property wrapper around String[] @@ -2238,7 +2310,7 @@ The unit is in density-independent pixels only for SDF sprites that were created } /** - * Font stack to use for displaying text. + * Fonts to use for displaying text. If the `glyphs` root property is specified, this array is joined together and interpreted as a font stack name. Otherwise, it is interpreted as a cascading fallback list of local font names. * * @param value a String[] value * @return property wrapper around String[] @@ -2395,7 +2467,9 @@ The unit is in density-independent pixels only for SDF sprites that were created When present, this property takes precedence over {@link Property.TEXT_ANCHOR}, {@link PropertyFactory#textVariableAnchor}, {@link PropertyFactory#textOffset}, and {@link PropertyFactory#textRadialOffset}. ```json + { "text-variable-anchor-offset": ["top", [0, 4], "left", [3,0], "bottom", [1, 1]] } + ``` When the renderer chooses the `top` anchor, `[0, 4]` will be used for {@link PropertyFactory#textOffset}; the text will be shifted down by 4 ems. @@ -2417,7 +2491,9 @@ The unit is in density-independent pixels only for SDF sprites that were created When present, this property takes precedence over {@link Property.TEXT_ANCHOR}, {@link PropertyFactory#textVariableAnchor}, {@link PropertyFactory#textOffset}, and {@link PropertyFactory#textRadialOffset}. ```json + { "text-variable-anchor-offset": ["top", [0, 4], "left", [3,0], "bottom", [1, 1]] } + ``` When the renderer chooses the `top` anchor, `[0, 4]` will be used for {@link PropertyFactory#textOffset}; the text will be shifted down by 4 ems. diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/integration/ViewPagerScrollTest.kt b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/integration/ViewPagerScrollTest.kt index db51f74a0..504547430 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/integration/ViewPagerScrollTest.kt +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/integration/ViewPagerScrollTest.kt @@ -3,9 +3,10 @@ package org.maplibre.android.integration import androidx.test.filters.LargeTest import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import androidx.test.rule.ActivityTestRule -import androidx.test.uiautomator.UiSelector -import org.junit.Ignore +import androidx.test.uiautomator.By +import androidx.test.uiautomator.Until import org.maplibre.android.testapp.activity.fragment.ViewPagerActivity +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -19,6 +20,17 @@ class ViewPagerScrollTest : BaseIntegrationTest() { @get:Rule var activityRule: ActivityTestRule = ActivityTestRule(ViewPagerActivity::class.java) + @Before + override fun beforeTest() { + super.beforeTest() + // Wait for the initial page tab to be visible before starting the test + // This ensures the ViewPager and PagerTabStrip are fully laid out + val initialTabVisible = device.wait(Until.hasObject(By.text("Page 0")), TIMEOUT_LONG) + if (!initialTabVisible) { + throw AssertionError("ViewPager tabs not visible after ${TIMEOUT_LONG}ms - Page 0 not found") + } + } + @Test @LargeTest fun scrollViewPager() { @@ -32,6 +44,19 @@ class ViewPagerScrollTest : BaseIntegrationTest() { } private fun clickTab(index: Int) { - device.findObject(UiSelector().text("Page $index")).click() + val text = "Page $index" + val found = device.wait(Until.hasObject(By.text(text)), TIMEOUT_UI_SEARCH_WAIT) + if (!found) { + throw AssertionError("Tab '$text' not found after ${TIMEOUT_UI_SEARCH_WAIT}ms") + } + val tab = device.findObject(By.text(text)) + ?: throw AssertionError("Tab '$text' was detected but findObject returned null") + tab.click() + // Small delay to allow ViewPager to settle after tab click + device.waitForIdle(500) + } + + companion object { + private const val TIMEOUT_LONG = 15000L } } diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/location/LocationIndicatorLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/location/LocationIndicatorLayerTest.java index d05d1f84a..fa71b407b 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/location/LocationIndicatorLayerTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/location/LocationIndicatorLayerTest.java @@ -186,7 +186,7 @@ public class LocationIndicatorLayerTest extends BaseLayerTest { // Set and Get Double[] propertyValue = new Double[] {0.0, 0.0, 0.0}; layer.setProperties(location(propertyValue)); - assertEquals(layer.getLocation().getValue(), propertyValue); + assertArrayEquals(layer.getLocation().getValue(), propertyValue); } @Test diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/CircleLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/CircleLayerTest.java index e4949786a..fd5e1fbe7 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/CircleLayerTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/CircleLayerTest.java @@ -386,7 +386,7 @@ public class CircleLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(circleTranslate(propertyValue)); - assertEquals(layer.getCircleTranslate().getValue(), propertyValue); + assertArrayEquals(layer.getCircleTranslate().getValue(), propertyValue); } @Test diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/ColorReliefLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/ColorReliefLayerTest.java new file mode 100644 index 000000000..83aa619b5 --- /dev/null +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/ColorReliefLayerTest.java @@ -0,0 +1,202 @@ +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +package org.maplibre.android.testapp.style; + +import android.graphics.Color; +import androidx.test.annotation.UiThreadTest; +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; + +import org.maplibre.geojson.LineString; +import org.maplibre.geojson.MultiLineString; +import org.maplibre.geojson.MultiPoint; +import org.maplibre.geojson.MultiPolygon; +import org.maplibre.geojson.Point; +import org.maplibre.geojson.Polygon; +import org.maplibre.android.maps.BaseLayerTest; +import org.junit.Before; +import timber.log.Timber; + +import org.maplibre.android.style.expressions.Expression; +import org.maplibre.android.style.layers.ColorReliefLayer; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.maplibre.android.style.expressions.Expression.*; +import static org.junit.Assert.*; +import static org.maplibre.android.style.layers.Property.*; +import static org.maplibre.android.style.layers.PropertyFactory.*; + +import org.maplibre.android.style.layers.TransitionOptions; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Basic smoke tests for ColorReliefLayer + */ +@RunWith(AndroidJUnit4ClassRunner.class) +public class ColorReliefLayerTest extends BaseLayerTest { + + private ColorReliefLayer layer; + private final List pointsList = new ArrayList() { + { + add(Point.fromLngLat(55.30122473231012, 25.26476622289597)); + add(Point.fromLngLat(55.29743486255916, 25.25827212207261)); + add(Point.fromLngLat(55.28978863411328, 25.251356725509737)); + add(Point.fromLngLat(55.300027931336984, 25.246425506635504)); + add(Point.fromLngLat(55.307474692951274, 25.244200378933655)); + add(Point.fromLngLat(55.31212891895635, 25.256408010450187)); + add(Point.fromLngLat(55.30774064871093, 25.26266169122738)); + add(Point.fromLngLat(55.301357710197806, 25.264946609615492)); + add(Point.fromLngLat(55.30122473231012, 25.26476622289597)); + } + }; + + @Before + @UiThreadTest + public void beforeTest(){ + super.before(); + layer = new ColorReliefLayer("my-layer", "composite"); + layer.setSourceLayer("composite"); + setupLayer(layer); + } + + @Test + @UiThreadTest + public void testSourceId() { + Timber.i("SourceId"); + assertNotNull(layer); + assertEquals(layer.getSourceId(), "composite"); + } + + @Test + @UiThreadTest + public void testSetVisibility() { + Timber.i("Visibility"); + assertNotNull(layer); + + // Get initial + assertEquals(layer.getVisibility().getValue(), VISIBLE); + + // Set + layer.setProperties(visibility(NONE)); + assertEquals(layer.getVisibility().getValue(), NONE); + } + + @Test + @UiThreadTest + public void testSourceLayer() { + Timber.i("SourceLayer"); + assertNotNull(layer); + + // Get initial + assertEquals(layer.getSourceLayer(), "composite"); + + // Set + final String sourceLayer = "test"; + layer.setSourceLayer(sourceLayer); + assertEquals(layer.getSourceLayer(), sourceLayer); + } + + @Test + @UiThreadTest + public void testFilter() { + Timber.i("Filter"); + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // Set + Expression filter = eq(get("undefined"), literal(1.0)); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + + // Set constant + filter = literal(true); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + } + + @Test + @UiThreadTest + public void testFilterDistance() { + Timber.i("FilterDistance"); + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + // distance with Point + Expression filter = lt(distance(Point.fromLngLat(1.0, 1.0)), 50); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + + // distance with LineString + filter = lt(distance(LineString.fromLngLats(pointsList)), 50); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + + // distance with MultiPoint + filter = lt(distance(MultiPoint.fromLngLats(pointsList)), 50); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + + // distance with MultiPoint + filter = lt(distance(MultiLineString.fromLngLats(Collections.singletonList(pointsList))), 50); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + + // distance with Polygon + filter = lt(distance(Polygon.fromLngLats(Collections.singletonList(pointsList))), 50); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + + // distance with MultiPolygon + filter = lt(distance(MultiPolygon.fromLngLats(Collections + .singletonList(Collections.singletonList(pointsList)))), 50); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + } + + @Test + @UiThreadTest + public void testFilterWithin() { + Timber.i("FilterWithin"); + assertNotNull(layer); + + // Get initial + assertEquals(layer.getFilter(), null); + + Expression filter = within(Polygon.fromLngLats(Collections.singletonList(pointsList))); + layer.setFilter(filter); + assertEquals(layer.getFilter().toString(), filter.toString()); + } + + + @Test + @UiThreadTest + public void testColorReliefOpacityTransition() { + Timber.i("color-relief-opacityTransitionOptions"); + assertNotNull(layer); + + // Set and Get + TransitionOptions options = new TransitionOptions(300, 100); + layer.setColorReliefOpacityTransition(options); + assertEquals(layer.getColorReliefOpacityTransition(), options); + } + + @Test + @UiThreadTest + public void testColorReliefOpacityAsConstant() { + Timber.i("color-relief-opacity"); + assertNotNull(layer); + assertNull(layer.getColorReliefOpacity().getValue()); + + // Set and Get + Float propertyValue = 0.3f; + layer.setProperties(colorReliefOpacity(propertyValue)); + assertEquals(layer.getColorReliefOpacity().getValue(), propertyValue); + } +} diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillExtrusionLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillExtrusionLayerTest.java index f966f0943..84a2ebb2a 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillExtrusionLayerTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillExtrusionLayerTest.java @@ -271,7 +271,7 @@ public class FillExtrusionLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(fillExtrusionTranslate(propertyValue)); - assertEquals(layer.getFillExtrusionTranslate().getValue(), propertyValue); + assertArrayEquals(layer.getFillExtrusionTranslate().getValue(), propertyValue); } @Test diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillLayerTest.java index a73ecf856..a768d2135 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillLayerTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/FillLayerTest.java @@ -372,7 +372,7 @@ public class FillLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(fillTranslate(propertyValue)); - assertEquals(layer.getFillTranslate().getValue(), propertyValue); + assertArrayEquals(layer.getFillTranslate().getValue(), propertyValue); } @Test diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/HillshadeLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/HillshadeLayerTest.java index 4deee30ff..2bac89b09 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/HillshadeLayerTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/HillshadeLayerTest.java @@ -92,9 +92,22 @@ public class HillshadeLayerTest extends BaseLayerTest { assertNull(layer.getHillshadeIlluminationDirection().getValue()); // Set and Get - Float propertyValue = 0.3f; + Float[] propertyValue = new Float[] {1.0f, 2.0f}; layer.setProperties(hillshadeIlluminationDirection(propertyValue)); - assertEquals(layer.getHillshadeIlluminationDirection().getValue(), propertyValue); + assertArrayEquals(layer.getHillshadeIlluminationDirection().getValue(), propertyValue); + } + + @Test + @UiThreadTest + public void testHillshadeIlluminationAltitudeAsConstant() { + Timber.i("hillshade-illumination-altitude"); + assertNotNull(layer); + assertNull(layer.getHillshadeIlluminationAltitude().getValue()); + + // Set and Get + Float[] propertyValue = new Float[] {1.0f, 2.0f}; + layer.setProperties(hillshadeIlluminationAltitude(propertyValue)); + assertArrayEquals(layer.getHillshadeIlluminationAltitude().getValue(), propertyValue); } @Test @@ -155,20 +168,9 @@ public class HillshadeLayerTest extends BaseLayerTest { assertNull(layer.getHillshadeShadowColor().getValue()); // Set and Get - String propertyValue = "rgba(255,128,0,0.7)"; + String[] propertyValue = new String[] {"rgba(255,0,0,1)", "rgba(0,0,255,1)"}; layer.setProperties(hillshadeShadowColor(propertyValue)); - assertEquals(layer.getHillshadeShadowColor().getValue(), propertyValue); - } - - @Test - @UiThreadTest - public void testHillshadeShadowColorAsIntConstant() { - Timber.i("hillshade-shadow-color"); - assertNotNull(layer); - - // Set and Get - layer.setProperties(hillshadeShadowColor(Color.argb(127, 255, 127, 0))); - assertEquals(layer.getHillshadeShadowColorAsInt(), Color.argb(127, 255, 127, 0)); + assertArrayEquals(layer.getHillshadeShadowColor().getValue(), propertyValue); } @Test @@ -191,20 +193,9 @@ public class HillshadeLayerTest extends BaseLayerTest { assertNull(layer.getHillshadeHighlightColor().getValue()); // Set and Get - String propertyValue = "rgba(255,128,0,0.7)"; + String[] propertyValue = new String[] {"rgba(255,0,0,1)", "rgba(0,0,255,1)"}; layer.setProperties(hillshadeHighlightColor(propertyValue)); - assertEquals(layer.getHillshadeHighlightColor().getValue(), propertyValue); - } - - @Test - @UiThreadTest - public void testHillshadeHighlightColorAsIntConstant() { - Timber.i("hillshade-highlight-color"); - assertNotNull(layer); - - // Set and Get - layer.setProperties(hillshadeHighlightColor(Color.argb(127, 255, 127, 0))); - assertEquals(layer.getHillshadeHighlightColorAsInt(), Color.argb(127, 255, 127, 0)); + assertArrayEquals(layer.getHillshadeHighlightColor().getValue(), propertyValue); } @Test @@ -242,4 +233,17 @@ public class HillshadeLayerTest extends BaseLayerTest { layer.setProperties(hillshadeAccentColor(Color.argb(127, 255, 127, 0))); assertEquals(layer.getHillshadeAccentColorAsInt(), Color.argb(127, 255, 127, 0)); } + + @Test + @UiThreadTest + public void testHillshadeMethodAsConstant() { + Timber.i("hillshade-method"); + assertNotNull(layer); + assertNull(layer.getHillshadeMethod().getValue()); + + // Set and Get + String propertyValue = HILLSHADE_METHOD_STANDARD; + layer.setProperties(hillshadeMethod(propertyValue)); + assertEquals(layer.getHillshadeMethod().getValue(), propertyValue); + } } diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/LineLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/LineLayerTest.java index dc4d6b521..f0cadf77a 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/LineLayerTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/LineLayerTest.java @@ -375,7 +375,7 @@ public class LineLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(lineTranslate(propertyValue)); - assertEquals(layer.getLineTranslate().getValue(), propertyValue); + assertArrayEquals(layer.getLineTranslate().getValue(), propertyValue); } @Test @@ -552,7 +552,7 @@ public class LineLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {}; layer.setProperties(lineDasharray(propertyValue)); - assertEquals(layer.getLineDasharray().getValue(), propertyValue); + assertArrayEquals(layer.getLineDasharray().getValue(), propertyValue); } @Test diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java index 62a9752d9..e40283e4c 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/SymbolLayerTest.java @@ -356,7 +356,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f, 0f, 0f}; layer.setProperties(iconTextFitPadding(propertyValue)); - assertEquals(layer.getIconTextFitPadding().getValue(), propertyValue); + assertArrayEquals(layer.getIconTextFitPadding().getValue(), propertyValue); } @Test @@ -424,11 +424,11 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = {2.0f, 2.0f, 2.0f, 2.0f}; layer.setProperties(iconPadding(propertyValue)); - assertEquals(layer.getIconPadding().getValue(), propertyValue); + assertArrayEquals(layer.getIconPadding().getValue(), propertyValue); // Single number value can be used too for backward compatibility Float number = propertyValue[0] + 1.0f; layer.setProperties(iconPadding(number)); - assertEquals(layer.getIconPadding().getValue(), new Float[]{number, number, number, number}); + assertArrayEquals(layer.getIconPadding().getValue(), new Float[]{number, number, number, number}); } @Test @@ -467,7 +467,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(iconOffset(propertyValue)); - assertEquals(layer.getIconOffset().getValue(), propertyValue); + assertArrayEquals(layer.getIconOffset().getValue(), propertyValue); } @Test @@ -576,7 +576,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get String[] propertyValue = new String[]{"Open Sans Regular", "Arial Unicode MS Regular"}; layer.setProperties(textFont(propertyValue)); - assertEquals(layer.getTextFont().getValue(), propertyValue); + assertArrayEquals(layer.getTextFont().getValue(), propertyValue); } @Test @@ -719,7 +719,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get String[] propertyValue = new String[0]; layer.setProperties(textVariableAnchor(propertyValue)); - assertEquals(layer.getTextVariableAnchor().getValue(), propertyValue); + assertArrayEquals(layer.getTextVariableAnchor().getValue(), propertyValue); } @Test @@ -732,7 +732,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get Object[] propertyValue = new Object[] {"top", new Float[]{1f, 2f}}; layer.setProperties(textVariableAnchorOffset(propertyValue)); - assertEquals(layer.getTextVariableAnchorOffset().getValue(), propertyValue); + assertArrayEquals(layer.getTextVariableAnchorOffset().getValue(), propertyValue); } @Test @@ -784,7 +784,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get String[] propertyValue = new String[0]; layer.setProperties(textWritingMode(propertyValue)); - assertEquals(layer.getTextWritingMode().getValue(), propertyValue); + assertArrayEquals(layer.getTextWritingMode().getValue(), propertyValue); } @Test @@ -875,7 +875,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(textOffset(propertyValue)); - assertEquals(layer.getTextOffset().getValue(), propertyValue); + assertArrayEquals(layer.getTextOffset().getValue(), propertyValue); } @Test @@ -1164,7 +1164,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(iconTranslate(propertyValue)); - assertEquals(layer.getIconTranslate().getValue(), propertyValue); + assertArrayEquals(layer.getIconTranslate().getValue(), propertyValue); } @Test @@ -1414,7 +1414,7 @@ public class SymbolLayerTest extends BaseLayerTest { // Set and Get Float[] propertyValue = new Float[] {0f, 0f}; layer.setProperties(textTranslate(propertyValue)); - assertEquals(layer.getTextTranslate().getValue(), propertyValue); + assertArrayEquals(layer.getTextTranslate().getValue(), propertyValue); } @Test diff --git a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/layer.junit.ejs b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/layer.junit.ejs index 6d53209a1..ff0ae4e7d 100644 --- a/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/layer.junit.ejs +++ b/platform/android/MapLibreAndroidTestApp/src/androidTest/java/org/maplibre/android/testapp/style/layer.junit.ejs @@ -231,12 +231,12 @@ public class <%- camelize(type) %>LayerTest extends BaseLayerTest { assertEquals(layer.get<%- camelize(property.name) %>().getValue(), propertyValue); <% } -%> layer.setProperties(<%- camelizeWithLeadingLowercase(property.name) %>(propertyValue)); - assertEquals(layer.get<%- camelize(property.name) %>().getValue(), propertyValue); + <%- propertyType(property).endsWith('[]') ? 'assertArrayEquals' : 'assertEquals' %>(layer.get<%- camelize(property.name) %>().getValue(), propertyValue); <% if (property.type === 'padding') { -%> // Single number value can be used too for backward compatibility Float number = propertyValue[0] + 1.0f; layer.setProperties(<%- camelizeWithLeadingLowercase(property.name) %>(number)); - assertEquals(layer.get<%- camelize(property.name) %>().getValue(), new Float[]{number, number, number, number}); + assertArrayEquals(layer.get<%- camelize(property.name) %>().getValue(), new Float[]{number, number, number, number}); <% } -%> <% if (property.tokens) { -%> diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/ViewPagerActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/ViewPagerActivity.kt index d9b23ec50..a59427ea4 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/ViewPagerActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/fragment/ViewPagerActivity.kt @@ -23,7 +23,11 @@ class ViewPagerActivity : AppCompatActivity() { super.onCreate(savedInstanceState) binding = ActivityViewpagerBinding.inflate(layoutInflater) setContentView(binding.root) - binding.viewPager.adapter = MapFragmentAdapter(this, supportFragmentManager) + val adapter = MapFragmentAdapter(this, supportFragmentManager) + binding.viewPager.adapter = adapter + // Ensure all pages are instantiated so their titles are available to UI tests immediately + // This keeps all 5 fragments in memory so PagerTabStrip shows all titles + binding.viewPager.offscreenPageLimit = adapter.count } override fun onRestoreInstanceState(savedInstanceState: Bundle) { diff --git a/platform/android/scripts/generate-style-code.mjs b/platform/android/scripts/generate-style-code.mjs index 53b65babb..401da28ef 100644 --- a/platform/android/scripts/generate-style-code.mjs +++ b/platform/android/scripts/generate-style-code.mjs @@ -119,6 +119,10 @@ global.propertyType = function propertyType(property) { return 'String'; case 'color': return 'String'; + case 'numberArray': + return 'Float[]'; + case 'colorArray': + return 'String[]'; case 'padding': return 'Float[]'; case 'variableAnchorOffsetCollection': @@ -145,6 +149,10 @@ global.propertyJavaType = function propertyType(property) { return 'String'; case 'color': return 'String'; + case 'numberArray': + return 'float[]'; + case 'colorArray': + return 'String[]'; case 'array': return `${propertyJavaType({type:property.value})}[]`; default: @@ -164,6 +172,10 @@ global.propertyJNIType = function propertyJNIType(property) { return 'String'; case 'color': return 'String'; + case 'numberArray': + return 'jarray'; + case 'colorArray': + return 'jarray'; case 'array': return `jarray<${propertyType({type:property.value})}[]>`; default: @@ -197,6 +209,10 @@ global.propertyNativeType = function (property) { return `${camelize(property.name)}Type`; case 'color': return `Color`; + case 'numberArray': + return 'std::vector'; + case 'colorArray': + return 'std::vector'; case 'padding': return 'Padding'; case 'variableAnchorOffsetCollection': @@ -237,6 +253,10 @@ global.defaultExpressionJava = function(property) { return "string"; case 'color': return 'toColor'; + case 'numberArray': + return 'array'; + case 'colorArray': + return 'array'; case 'padding': return 'toPadding'; case 'array': @@ -271,6 +291,10 @@ global.defaultValueJava = function(property) { return snakeCaseUpper(property.name) + "_" + snakeCaseUpper(Object.keys(property.values)[0]); case 'color': return '"rgba(255,128,0,0.7)"'; + case 'numberArray': + return 'new Float[] {1.0f, 2.0f}'; + case 'colorArray': + return 'new String[] {"rgba(255,0,0,1)", "rgba(0,0,255,1)"}'; case 'padding': return '{2.0f, 2.0f, 2.0f, 2.0f}'; case 'variableAnchorOffsetCollection': @@ -409,6 +433,10 @@ global.evaluatedType = function (property) { return (isLightProperty(property) ? 'Light' : '') + `${camelize(property.name)}Type`; case 'color': return `Color`; + case 'numberArray': + return 'std::vector'; + case 'colorArray': + return 'std::vector'; case 'array': if (property.length) { return `std::array<${evaluatedType({type: property.value})}, ${property.length}>`; diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index 7ee28ac9b..8a7a16685 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -2,6 +2,7 @@ MLN_GENERATED_DARWIN_STYLE_SOURCE = [ "src/MLNLight.mm", "src/MLNBackgroundStyleLayer.mm", "src/MLNCircleStyleLayer.mm", + "src/MLNColorReliefStyleLayer.mm", "src/MLNFillExtrusionStyleLayer.mm", "src/MLNFillStyleLayer.mm", "src/MLNHeatmapStyleLayer.mm", @@ -13,6 +14,7 @@ MLN_GENERATED_DARWIN_STYLE_SOURCE = [ MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS = [ "src/MLNBackgroundStyleLayer.h", + "src/MLNColorReliefStyleLayer.h", "src/MLNFillExtrusionStyleLayer.h", "src/MLNHeatmapStyleLayer.h", "src/MLNLight.h", @@ -27,6 +29,7 @@ MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS = [ MLN_GENERATED_DARWIN_STYLE_HEADERS = [ "src/MLNRasterStyleLayer_Private.h", "src/MLNBackgroundStyleLayer_Private.h", + "src/MLNColorReliefStyleLayer_Private.h", "src/MLNFillExtrusionStyleLayer_Private.h", "src/MLNHeatmapStyleLayer_Private.h", "src/MLNLineStyleLayer_Private.h", @@ -38,6 +41,7 @@ MLN_GENERATED_DARWIN_STYLE_HEADERS = [ MLN_GENERATED_DARWIN_TEST_CODE = [ "test/MLNBackgroundStyleLayerTests.mm", + "test/MLNColorReliefStyleLayerTests.mm", "test/MLNFillExtrusionStyleLayerTests.mm", "test/MLNHeatmapStyleLayerTests.mm", "test/MLNLightTest.mm", diff --git a/platform/darwin/darwin.cmake b/platform/darwin/darwin.cmake index 1bdab18ae..d9bc2c707 100644 --- a/platform/darwin/darwin.cmake +++ b/platform/darwin/darwin.cmake @@ -116,6 +116,7 @@ set(MLN_GENERATED_DARWIN_STYLE_SOURCE "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNLight.mm" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNBackgroundStyleLayer.mm" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNCircleStyleLayer.mm" + "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNColorReliefStyleLayer.mm" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNFillExtrusionStyleLayer.mm" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNFillStyleLayer.mm" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNHeatmapStyleLayer.mm" @@ -127,6 +128,7 @@ set(MLN_GENERATED_DARWIN_STYLE_SOURCE set(MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNBackgroundStyleLayer.h" + "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNColorReliefStyleLayer.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNFillExtrusionStyleLayer.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNHeatmapStyleLayer.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNLight.h" @@ -141,6 +143,7 @@ set(MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS set(MLN_GENERATED_DARWIN_STYLE_HEADERS "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNRasterStyleLayer_Private.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNBackgroundStyleLayer_Private.h" + "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNColorReliefStyleLayer_Private.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNFillExtrusionStyleLayer_Private.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNHeatmapStyleLayer_Private.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNLineStyleLayer_Private.h" diff --git a/platform/darwin/scripts/generate-style-code.mjs b/platform/darwin/scripts/generate-style-code.mjs index 82c6bc4e5..98f0f5ec5 100644 --- a/platform/darwin/scripts/generate-style-code.mjs +++ b/platform/darwin/scripts/generate-style-code.mjs @@ -206,6 +206,10 @@ global.objCTestValue = function (property, layerType, arraysAsStructs, indent) { return `@"'${_.last(_.keys(property.values))}'"`; case 'color': return '@"%@", [MLNColor redColor]'; + case 'numberArray': + return '@"{1, 2}"'; + case 'colorArray': + return '@"%@", @[[MLNColor redColor], [MLNColor blueColor]]'; case 'padding': return paddingTestValue(); case 'variableAnchorOffsetCollection': @@ -271,6 +275,10 @@ global.mbglTestValue = function (property, layerType) { } case 'color': return '{ 1, 0, 0, 1 }'; + case 'numberArray': + return '{1, 2}'; + case 'colorArray': + return '{ { 1, 0, 0, 1 }, { 0, 0, 1, 1 } }'; case 'padding': return '{ 1, 1, 1, 1 }'; case 'array': @@ -348,6 +356,10 @@ global.testHelperMessage = function (property, layerType, isFunction) { return `testEnum${fnSuffix}:${objCEnum} type:@encode(${objCType})`; case 'color': return 'testColor' + fnSuffix; + case 'numberArray': + return 'testNumberArray' + fnSuffix; + case 'colorArray': + return 'testColorArray' + fnSuffix; case 'padding': return 'testPaddingType' + fnSuffix; case 'array': @@ -532,6 +544,10 @@ global.describeType = function (property) { return '`MLN' + camelize(property.name) + '`'; case 'color': return '`UIColor`'; + case 'numberArray': + return 'numeric array'; + case 'colorArray': + return '`UIColor` array'; case 'padding': return '`UIEdgeInsets`'; case 'array': @@ -630,6 +646,14 @@ global.describeValue = function (value, property, layerType) { } return 'a `UIColor`' + ` object whose RGB value is ${formatNumber(color.r)}, ${formatNumber(color.g)}, ${formatNumber(color.b)} and whose alpha value is ${formatNumber(color.a)}`; + case 'numberArray': + if (Array.isArray(value)) { + return 'an array containing the numeric values `' + value.map(formatNumber).join('`, `') + '`'; + } + return 'an array of numeric values'; + case 'colorArray': + return 'an array of `UIColor` objects'; + case 'padding': return describePadding(); @@ -690,6 +714,10 @@ global.propertyType = function (property) { return 'NSValue *'; case 'color': return 'MLNColor *'; + case 'numberArray': + return 'NSArray *'; + case 'colorArray': + return 'NSArray *'; case 'padding': return 'NSValue *'; case 'array': @@ -742,6 +770,10 @@ global.valueTransformerArguments = function (property) { return [mbglType(property), 'NSValue *', mbglType(property), `MLN${camelize(property.name)}`]; case 'color': return ['mbgl::Color', objCType]; + case 'numberArray': + return ['std::vector', objCType, 'float']; + case 'colorArray': + return ['std::vector', objCType, 'mbgl::Color']; case 'padding': return ['mbgl::Padding', objCType]; case 'variableAnchorOffsetCollection': @@ -801,6 +833,10 @@ global.mbglType = function(property) { } case 'color': return 'mbgl::Color'; + case 'numberArray': + return 'std::vector'; + case 'colorArray': + return 'std::vector'; case 'padding': return 'mbgl::Padding'; case 'variableAnchorOffsetCollection': @@ -896,7 +932,7 @@ const layers = _(spec.layer.type.values).map((value, layerType) => { }).sortBy(['type']).value(); function duplicatePlatformDecls(src) { - // Look for a documentation comment that contains “MLNColor” or “UIColor” + // Look for a documentation comment that contains "MLNColor" or "UIColor" // and the subsequent function, method, or property declaration. Try not to // match greedily. return src.replace(/(\/\*\*(?:\*[^\/]|[^*])*?\b(?:MLN|NS|UI)Color\b[\s\S]*?\*\/)(\s*.+?;)/g, diff --git a/platform/darwin/src/MLNRasterDEMSource.h b/platform/darwin/src/MLNRasterDEMSource.h index 71a3862bf..be6e6cec8 100644 --- a/platform/darwin/src/MLNRasterDEMSource.h +++ b/platform/darwin/src/MLNRasterDEMSource.h @@ -42,8 +42,9 @@ typedef NS_ENUM(NSUInteger, MLNDEMEncoding) { conforms to the TileJSON specification. A raster DEM source is added to an ``MLNStyle`` object along with one or more - ``MLNHillshadeStyleLayer`` objects. Use a hillshade style layer to control the - appearance of content supplied by the raster DEM source. + ``MLNHillshadeStyleLayer`` or ``MLNColorReliefStyleLayer`` objects. Use a hillshade + style layer to control the appearance of content supplied by the raster DEM source, + or a color relief style layer to render elevation data with a color ramp. Each raster-dem diff --git a/platform/darwin/src/MLNStyle.mm b/platform/darwin/src/MLNStyle.mm index 7c29cfc94..dd225ef38 100644 --- a/platform/darwin/src/MLNStyle.mm +++ b/platform/darwin/src/MLNStyle.mm @@ -2,6 +2,7 @@ #import "MLNBackgroundStyleLayer.h" #import "MLNCircleStyleLayer.h" +#import "MLNColorReliefStyleLayer.h" #import "MLNFillExtrusionStyleLayer.h" #import "MLNFillStyleLayer.h" #import "MLNHeatmapStyleLayer.h" diff --git a/platform/darwin/src/MLNStyleLayer.h.ejs b/platform/darwin/src/MLNStyleLayer.h.ejs index 50d17ffa4..5ea2d1e68 100644 --- a/platform/darwin/src/MLNStyleLayer.h.ejs +++ b/platform/darwin/src/MLNStyleLayer.h.ejs @@ -12,7 +12,7 @@ #import "MLNFoundation.h" #import "MLN<%- (type === 'background' ? '' : - (type === 'raster' || type === 'hillshade' ? 'Foreground' : + (type === 'raster' || type === 'hillshade' || type === 'color-relief' ? 'Foreground' : 'Vector')) %>StyleLayer.h" @@ -84,7 +84,7 @@ typedef NS_ENUM(NSUInteger, MLN<%- camelize(enumName(property)) %>) { MLN_EXPORT @interface MLN<%- camelize(type) %>StyleLayer : MLN<%- (type === 'background' ? '' : - (type === 'raster' || type === 'hillshade' ? 'Foreground' : + (type === 'raster' || type === 'hillshade' || type === 'color-relief' ? 'Foreground' : 'Vector')) %>StyleLayer <% if (type === 'background') { -%> diff --git a/platform/darwin/src/MLNStyleLayer.mm.ejs b/platform/darwin/src/MLNStyleLayer.mm.ejs index c706aa02e..13ac36129 100644 --- a/platform/darwin/src/MLNStyleLayer.mm.ejs +++ b/platform/darwin/src/MLNStyleLayer.mm.ejs @@ -88,7 +88,7 @@ namespace mbgl { return @(self.rawLayer->getSourceID().c_str()); } -<% if (type !== 'raster' && type !== 'hillshade') { -%> +<% if (type !== 'raster' && type !== 'hillshade' && type !== 'color-relief') { -%> - (NSString *)sourceLayerIdentifier { MLNAssertStyleLayerIsValid(); diff --git a/platform/darwin/src/MLNStyleLayerManager.mm b/platform/darwin/src/MLNStyleLayerManager.mm index cb7ee76f5..7005c84ad 100644 --- a/platform/darwin/src/MLNStyleLayerManager.mm +++ b/platform/darwin/src/MLNStyleLayerManager.mm @@ -2,6 +2,7 @@ #import "MLNBackgroundStyleLayer_Private.h" #import "MLNCircleStyleLayer_Private.h" +#import "MLNColorReliefStyleLayer_Private.h" #import "MLNCustomStyleLayer_Private.h" #import "MLNFillExtrusionStyleLayer_Private.h" #import "MLNFillStyleLayer_Private.h" @@ -53,6 +54,11 @@ LayerManagerDarwin::LayerManagerDarwin() { #elif !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL) addLayerType(std::make_unique()); #endif +#if defined(MBGL_LAYER_COLOR_RELIEF_DISABLE_RUNTIME) + addLayerTypeCoreOnly(std::make_unique()); +#elif !defined(MBGL_LAYER_COLOR_RELIEF_DISABLE_ALL) + addLayerType(std::make_unique()); +#endif #if defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_RUNTIME) addLayerTypeCoreOnly(std::make_unique()); #elif !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL) diff --git a/platform/darwin/test/MLNStyleLayerTests.mm.ejs b/platform/darwin/test/MLNStyleLayerTests.mm.ejs index 225f32128..f1ad50acf 100644 --- a/platform/darwin/test/MLNStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MLNStyleLayerTests.mm.ejs @@ -27,7 +27,7 @@ return @"<%- type %>"; } -<% if (type !== 'background' && type !== 'raster' && type !== 'hillshade') { -%> +<% if (type !== 'background' && type !== 'raster' && type !== 'hillshade' && type !== 'color-relief') { -%> - (void)testPredicates { MLNPointFeature *feature = [[MLNPointFeature alloc] init]; MLNShapeSource *source = [[MLNShapeSource alloc] initWithIdentifier:@"sourceID" shape:feature options:nil]; diff --git a/platform/default/src/mbgl/layermanager/layer_manager.cpp b/platform/default/src/mbgl/layermanager/layer_manager.cpp index 988c105eb..7949c169f 100644 --- a/platform/default/src/mbgl/layermanager/layer_manager.cpp +++ b/platform/default/src/mbgl/layermanager/layer_manager.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -69,6 +70,9 @@ LayerManagerDefault::LayerManagerDefault() { #if !defined(MBGL_LAYER_HILLSHADE_DISABLE_ALL) addLayerType(std::make_unique()); #endif +#if !defined(MBGL_LAYER_COLOR_RELIEF_DISABLE_ALL) + addLayerType(std::make_unique()); +#endif #if !defined(MBGL_LAYER_FILL_EXTRUSION_DISABLE_ALL) addLayerType(std::make_unique()); #endif diff --git a/platform/node/src/node_conversion.hpp b/platform/node/src/node_conversion.hpp index 267b5baec..358303d6f 100644 --- a/platform/node/src/node_conversion.hpp +++ b/platform/node/src/node_conversion.hpp @@ -121,6 +121,18 @@ public: return {Nan::To(value).ToChecked()}; } else if (value->IsNumber()) { return {Nan::To(value).ToChecked()}; + } else if (value->IsArray()) { + v8::Local array = value.As(); + std::vector result; + result.reserve(array->Length()); + for (uint32_t i = 0; i < array->Length(); ++i) { + v8::Local item = Nan::Get(array, i).ToLocalChecked(); + auto converted = toValue(item); + if (converted) { + result.push_back(*converted); + } + } + return {result}; } else { return {}; } diff --git a/platform/node/src/node_expression.cpp b/platform/node/src/node_expression.cpp index b2ab3e8d1..15269c682 100644 --- a/platform/node/src/node_expression.cpp +++ b/platform/node/src/node_expression.cpp @@ -51,7 +51,9 @@ type::Type parseType(v8::Local type) { {"formatted", type::Formatted}, {"number-format", type::String}, {"resolvedImage", type::Image}, - {"variableAnchorOffsetCollection", type::VariableAnchorOffsetCollection}}; + {"variableAnchorOffsetCollection", type::VariableAnchorOffsetCollection}, + {"numberArray", type::Array(type::Number)}, + {"colorArray", type::Array(type::Color)}}; v8::Local v8kind = Nan::Get(type, Nan::New("kind").ToLocalChecked()).ToLocalChecked(); std::string kind(*v8::String::Utf8Value(v8::Isolate::GetCurrent(), v8kind)); diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index b67ed90a1..6a61f4eec 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include diff --git a/scripts/generate-style-code.mjs b/scripts/generate-style-code.mjs index 9b96cd39e..1f1eca7d4 100644 --- a/scripts/generate-style-code.mjs +++ b/scripts/generate-style-code.mjs @@ -53,6 +53,10 @@ function expressionType(property) { case 'number': case 'enum': return 'NumberType'; + case 'numberArray': + return 'Array'; + case 'colorArray': + return 'Array'; case 'image': return 'ImageType'; case 'string': @@ -104,6 +108,10 @@ function evaluatedType(property) { return 'Rotation'; } return /location$/.test(property.name) ? 'double' : 'float'; + case 'numberArray': + return 'std::vector'; + case 'colorArray': + return 'std::vector'; case 'resolvedImage': return 'expression::Image'; case 'formatted': @@ -210,7 +218,7 @@ function propertyValueType(property) { * @returns {string} */ function formatNumber(property, num = 0) { - if (evaluatedType(property) === "float") { + if (evaluatedType(property) === "float" || property.type === "number") { const str = num.toString(); return str + (str.includes(".") ? "" : ".") + "f"; } @@ -237,6 +245,23 @@ function defaultValue(property) { switch (property.type) { case 'number': return formatNumber(property, property.default); + case 'numberArray': + // Default is a single number, wrap it in a vector + return `{${formatNumber({type: 'number'}, property.default)}}`; + case 'colorArray': + // Default is a single color string, parse it and wrap in a vector + const colorArray = parseCSSColor(property.default); + const colorStr = colorArray.join(', '); + switch (colorStr) { + case '0, 0, 0, 0': + return '{{}}'; + case '0, 0, 0, 1': + return '{Color::black()}'; + case '1, 1, 1, 1': + return '{Color::white()}'; + default: + return `{{Color{${colorStr}}}}`; + } case 'formatted': case 'string': case 'resolvedImage': diff --git a/scripts/style-spec-reference/v8.json b/scripts/style-spec-reference/v8.json index eb3867f32..6fd642d53 100644 --- a/scripts/style-spec-reference/v8.json +++ b/scripts/style-spec-reference/v8.json @@ -34,6 +34,18 @@ 40.7736 ] }, + "centerAltitude": { + "type": "number", + "doc": "Default map center altitude in meters above sea level. The style center altitude defines the altitude where the camera is looking at and will be used only if the map has not been positioned by other means (e.g. map options or user interaction).", + "example": 123.4, + "sdk-support": { + "basic functionality": { + "js": "5.0.0", + "android": "https://github.com/maplibre/maplibre-native/issues/2980", + "ios": "https://github.com/maplibre/maplibre-native/issues/2980" + } + } + }, "zoom": { "type": "number", "doc": "Default zoom level. The style zoom will be used only if the map has not been positioned by other means (e.g. map options or user interaction).", @@ -52,7 +64,38 @@ "default": 0, "units": "degrees", "doc": "Default pitch, in degrees. Zero is perpendicular to the surface, for a look straight down at the map, while a greater value like 60 looks ahead towards the horizon. The style pitch will be used only if the map has not been positioned by other means (e.g. map options or user interaction).", - "example": 50 + "example": 50, + "sdk-support": { + "0-60 degrees": { + "js": "0.8.0", + "android": "1.0.0", + "ios": "1.0.0" + }, + "0-85 degrees": { + "js": "2.0.0", + "android": "https://github.com/maplibre/maplibre-native/issues/1909", + "ios": "https://github.com/maplibre/maplibre-native/issues/1909" + }, + "0-180 degrees": { + "js": "5.0.0", + "android": "https://github.com/maplibre/maplibre-native/issues/1909", + "ios": "https://github.com/maplibre/maplibre-native/issues/1909" + } + } + }, + "roll": { + "type": "number", + "default": 0, + "units": "degrees", + "doc": "Default roll, in degrees. The roll angle is measured counterclockwise about the camera boresight. The style roll will be used only if the map has not been positioned by other means (e.g. map options or user interaction).", + "example": 45, + "sdk-support": { + "basic functionality": { + "js": "5.0.0", + "android": "https://github.com/maplibre/maplibre-native/issues/2941", + "ios": "https://github.com/maplibre/maplibre-native/issues/2941" + } + } }, "light": { "type": "light", @@ -65,7 +108,7 @@ }, "sky": { "type": "sky", - "doc": "The map's sky configuration. **Note:** this definition is still experimental and is under develoment in maplibre-gl-js.", + "doc": "The map's sky configuration. **Note:** this definition is still experimental and is under development in maplibre-gl-js.", "example": { "sky-color": "#199EF3", "sky-horizon-blend": 0.5, @@ -84,9 +127,15 @@ }, "projection": { "type": "projection", - "doc": "The projection configuration. **Note:** this definition is still experimental and is under development in maplibre-gl-js.", + "doc": "The projection configuration", "example": { - "type": "globe" + "type": [ + "interpolate", + ["linear"], + ["zoom"], + 10, "vertical-perspective", + 12, "mercator" + ] } }, "terrain": { @@ -134,8 +183,51 @@ }, "glyphs": { "type": "string", - "doc": "A URL template for loading signed-distance-field glyph sets in PBF format. \nThe URL must include:\n\n - `{fontstack}` - When requesting glyphs, this token is replaced with a comma separated list of fonts from a font stack specified in the text-font property of a symbol layer. \n\n - `{range}` - When requesting glyphs, this token is replaced with a range of 256 Unicode code points. For example, to load glyphs for the Unicode Basic Latin and Basic Latin-1 Supplement blocks, the range would be 0-255. The actual ranges that are loaded are determined at runtime based on what text needs to be displayed.\n\nThis property is required if any layer uses the `text-field` layout property. The URL must be absolute, containing the [scheme, authority and path components](https://en.wikipedia.org/wiki/URL#Syntax).", - "example": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf" + "doc": "A URL template for loading signed-distance-field glyph sets in PBF format.\n\nIf this property is set, any text in the `text-field` layout property is displayed in the font stack named by the `text-font` layout property based on glyphs located at the URL specified by this property. Otherwise, font faces will be determined by the `text-font` property based on the local environment.\n\nThe URL must include:\n\n - `{fontstack}` - When requesting glyphs, this token is replaced with a comma separated list of fonts from a font stack specified in the `text-font` property of a symbol layer. \n\n - `{range}` - When requesting glyphs, this token is replaced with a range of 256 Unicode code points. For example, to load glyphs for the Unicode Basic Latin and Basic Latin-1 Supplement blocks, the range would be 0-255. The actual ranges that are loaded are determined at runtime based on what text needs to be displayed.\n\nThe URL must be absolute, containing the [scheme, authority and path components](https://en.wikipedia.org/wiki/URL#Syntax).", + "example": "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf", + "sdk-support": { + "basic functionality": { + "js": "0.0.16", + "android": "0.1.1", + "ios": "0.1.0" + }, + "omit to use local fonts": { + "js": "https://github.com/maplibre/maplibre-gl-js/issues/3302", + "android": "https://github.com/maplibre/maplibre-native/issues/165", + "ios": "https://github.com/maplibre/maplibre-native/issues/165" + } + } + }, + "font-faces": { + "type": "array", + "value": "fontFaces", + "doc": "The `font-faces` property can be used to specify what font files to use for rendering text. Font faces contain information needed to render complex texts such as [Devanagari](https://en.wikipedia.org/wiki/Devanagari), [Khmer](https://en.wikipedia.org/wiki/Khmer_script) among many others.

Unicode range

The optional `unicode-range` property can be used to only use a particular font file for characters within the specified unicode range(s). Its value should be an array of strings, each indicating a start and end of a unicode range, similar to the [CSS descriptor with the same name](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range). This allows specifying multiple non-consecutive unicode ranges. When not specified, the default value is `U+0-10FFFF`, meaning the font file will be used for all unicode characters.\n\nRefer to the [Unicode Character Code Charts](https://www.unicode.org/charts/) to see ranges for scripts supported by Unicode. To see what unicode code-points are available in a font, use a tool like [FontDrop](https://fontdrop.info/).\n\n

Font Resolution

For every name in a symbol layer’s [`text-font`](./layers.md/#text-font) array, characters are matched if they are covered one of the by the font files in the corresponding entry of the `font-faces` map. Any still-unmatched characters then fall back to the [`glyphs`](./glyphs.md) URL if provided.\n\n

Supported Fonts

What type of fonts are supported is implementation-defined. Unsupported fonts are ignored.", + "example": { + "Noto Sans Regular": [{ + "url": "https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansKhmer/hinted/ttf/NotoSansKhmer-Regular.ttf", + "unicode-range": ["U+1780-17FF"] + }, + { + "url": "https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansDevanagari/hinted/ttf/NotoSansDevanagari-Regular.ttf", + "unicode-range": ["U+0900-097F"] + }, + { + "url": "https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansMyanmar/hinted/ttf/NotoSansMyanmar-Regular.ttf", + "unicode-range": ["U+1000-109F"] + }, + { + "url": "https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansEthiopic/hinted/ttf/NotoSansEthiopic-Regular.ttf", + "unicode-range": ["U+1200-137F"] + }], + "Unifont": "https://ftp.gnu.org/gnu/unifont/unifont-15.0.01/unifont-15.0.01.ttf" + }, + "sdk-support": { + "basic functionality": { + "js": "https://github.com/maplibre/maplibre-gl-js/issues/50", + "android": "11.13.0", + "ios": "6.18.0" + } + } }, "transition": { "type": "transition", @@ -149,12 +241,12 @@ "required": true, "type": "array", "value": "layer", - "doc": "A style's `layers` property lists all the layers available in that style. The type of layer is specified by the `type` property, and must be one of `background`, `fill`, `line`, `symbol`, `raster`, `circle`, `fill-extrusion`, `heatmap`, `hillshade`.\n\nExcept for layers of the `background` type, each layer needs to refer to a source. Layers take the data that they get from a source, optionally filter features, and then define how those features are styled.", + "doc": "A style's `layers` property lists all the layers available in that style. The type of layer is specified by the `type` property, and must be one of `background`, `fill`, `line`, `symbol`, `raster`, `circle`, `fill-extrusion`, `heatmap`, `hillshade`, `color-relief`.\n\nExcept for layers of the `background` type, each layer needs to refer to a source. Layers take the data that they get from a source, optionally filter features, and then define how those features are styled.", "example": [ { "id": "coastline", "source": "maplibre", - "source-layer": "contries", + "source-layer": "countries", "type": "line", "paint": { "line-color": "#198EC8" @@ -247,7 +339,33 @@ "sdk-support": { "basic functionality": { "android": "9.3.0", - "ios": "5.10.0" + "ios": "5.10.0", + "js": "wontfix" + } + } + }, + "encoding": { + "type": "enum", + "values": { + "mvt": { + "doc": "Mapbox Vector Tiles. See http://github.com/mapbox/vector-tile-spec for more info." + }, + "mlt": { + "doc": "MapLibre Vector Tiles. See https://github.com/maplibre/maplibre-tile-spec for more info." + } + }, + "default": "mvt", + "doc": "The encoding used by this source. Mapbox Vector Tiles encoding is used by default.", + "sdk-support": { + "mvt": { + "android": "supported", + "ios": "supported", + "js": "supported" + }, + "mlt": { + "android": "https://github.com/maplibre/maplibre-native/issues/3721", + "ios": "https://github.com/maplibre/maplibre-native/issues/3721", + "js": "https://github.com/maplibre/maplibre-gl-js/issues/6258" } } }, @@ -328,7 +446,8 @@ "sdk-support": { "basic functionality": { "android": "9.3.0", - "ios": "5.10.0" + "ios": "5.10.0", + "js": "wontfix" } } }, @@ -403,7 +522,19 @@ } }, "default": "mapbox", - "doc": "The encoding used by this source. Mapbox Terrain RGB is used by default." + "doc": "The encoding used by this source. Mapbox Terrain RGB is used by default.", + "sdk-support": { + "mapbox, terrarium": { + "js": "0.43.0", + "ios": "6.0.0", + "android": "6.0.0" + }, + "custom": { + "js": "3.4.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/2783", + "android": "https://github.com/maplibre/maplibre-native/issues/2783" + } + } }, "redFactor": { "type": "number", @@ -411,7 +542,9 @@ "doc": "Value that will be multiplied by the red channel value when decoding. Only used on custom encodings.", "sdk-support": { "basic functionality": { - "js": "3.4" + "js": "3.4.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/2783", + "android": "https://github.com/maplibre/maplibre-native/issues/2783" } } }, @@ -421,7 +554,9 @@ "doc": "Value that will be multiplied by the blue channel value when decoding. Only used on custom encodings.", "sdk-support": { "basic functionality": { - "js": "3.4" + "js": "3.4.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/2783", + "android": "https://github.com/maplibre/maplibre-native/issues/2783" } } }, @@ -431,7 +566,9 @@ "doc": "Value that will be multiplied by the green channel value when decoding. Only used on custom encodings.", "sdk-support": { "basic functionality": { - "js": "3.4" + "js": "3.4.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/2358", + "android": "https://github.com/maplibre/maplibre-native/issues/2783" } } }, @@ -441,7 +578,9 @@ "doc": "Value that will be added to the encoding mix when decoding. Only used on custom encodings.", "sdk-support": { "basic functionality": { - "js": "3.4" + "js": "3.4.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/2783", + "android": "https://github.com/maplibre/maplibre-native/issues/2783" } } }, @@ -452,7 +591,8 @@ "sdk-support": { "basic functionality": { "android": "9.3.0", - "ios": "5.10.0" + "ios": "5.10.0", + "js": "wontfix" } } }, @@ -505,7 +645,7 @@ "cluster": { "type": "boolean", "default": false, - "doc": "If the data is a collection of point features, setting this to true clusters the points by radius into groups. Cluster groups become new `Point` features in the source with additional properties:\n * `cluster` Is `true` if the point is a cluster \n * `cluster_id` A unqiue id for the cluster to be used in conjunction with the [cluster inspection methods](https://maplibre.org/maplibre-gl-js/docs/API/classes/GeoJSONSource/#getclusterexpansionzoom)\n * `point_count` Number of original points grouped into this cluster\n * `point_count_abbreviated` An abbreviated point count" + "doc": "If the data is a collection of point features, setting this to true clusters the points by radius into groups. Cluster groups become new `Point` features in the source with additional properties:\n\n * `cluster` Is `true` if the point is a cluster \n\n * `cluster_id` A unique id for the cluster to be used in conjunction with the [cluster inspection methods](https://maplibre.org/maplibre-gl-js/docs/API/classes/GeoJSONSource/#getclusterexpansionzoom)\n\n * `point_count` Number of original points grouped into this cluster\n\n * `point_count_abbreviated` An abbreviated point count" }, "clusterRadius": { "type": "number", @@ -523,7 +663,7 @@ }, "clusterProperties": { "type": "*", - "doc": "An object defining custom properties on the generated clusters if clustering is enabled, aggregating values from clustered points. Has the form `{\"property_name\": [operator, map_expression]}`. `operator` is any expression function that accepts at least 2 operands (e.g. `\"+\"` or `\"max\"`) — it accumulates the property value from clusters/points the cluster contains; `map_expression` produces the value of a single point.\n\nExample: `{\"sum\": [\"+\", [\"get\", \"scalerank\"]]}`.\n\nFor more advanced use cases, in place of `operator`, you can use a custom reduce expression that references a special `[\"accumulated\"]` value, e.g.:\n`{\"sum\": [[\"+\", [\"accumulated\"], [\"get\", \"sum\"]], [\"get\", \"scalerank\"]]}`" + "doc": "An object defining custom properties on the generated clusters if clustering is enabled, aggregating values from clustered points. Has the form `{\"property_name\": [operator, map_expression]}`. `operator` is any expression function that accepts at least 2 operands (e.g. `\"+\"` or `\"max\"`) — it accumulates the property value from clusters/points the cluster contains; `map_expression` produces the value of a single point.\n\nExample: `{\"sum\": [\"+\", [\"get\", \"scalerank\"]]}`.\n\nFor more advanced use cases, in place of `operator`, you can use a custom reduce expression that references a special `[\"accumulated\"]` value, e.g.:\n\n`{\"sum\": [[\"+\", [\"accumulated\"], [\"get\", \"sum\"]], [\"get\", \"scalerank\"]]}`" }, "lineMetrics": { "type": "boolean", @@ -685,6 +825,21 @@ "js": "0.43.0", "android": "6.0.0", "ios": "4.0.0" + }, + "additional methods": { + "js": "5.5.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3396", + "ios": "https://github.com/maplibre/maplibre-native/issues/3396" + } + } + }, + "color-relief": { + "doc": "Client-side elevation coloring based on DEM data. The implementation supports Mapbox Terrain RGB, Mapzen Terrarium tiles and custom encodings.", + "sdk-support": { + "basic functionality": { + "js": "5.6.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3408", + "ios": "https://github.com/maplibre/maplibre-native/issues/3408" } } }, @@ -751,6 +906,7 @@ "layout_symbol", "layout_raster", "layout_hillshade", + "layout_color-relief", "layout_background" ], "layout_background": { @@ -1532,7 +1688,9 @@ "ios": "2.0.0" }, "data-driven styling": { - "js": "2.2.0" + "js": "2.2.0", + "android": "https://github.com/maplibre/maplibre-native/issues/2754", + "ios": "https://github.com/maplibre/maplibre-native/issues/2754" } }, "expression": { @@ -1769,7 +1927,9 @@ "ios": "3.4.0" }, "`viewport-glyph` value": { - "js": "2.1.8" + "js": "2.1.8", + "android": "https://github.com/maplibre/maplibre-native/issues/250", + "ios": "https://github.com/maplibre/maplibre-native/issues/250" } }, "expression": { @@ -1813,7 +1973,7 @@ "Open Sans Regular", "Arial Unicode MS Regular" ], - "doc": "Font stack to use for displaying text.", + "doc": "Fonts to use for displaying text. If the `glyphs` root property is specified, this array is joined together and interpreted as a font stack name. Otherwise, it is interpreted as a cascading fallback list of local font names.", "requires": [ "text-field" ], @@ -1827,6 +1987,11 @@ "js": "0.43.0", "android": "6.0.0", "ios": "4.0.0" + }, + "local fonts": { + "js": "https://github.com/maplibre/maplibre-gl-js/issues/3302", + "android": "https://github.com/maplibre/maplibre-native/issues/165", + "ios": "https://github.com/maplibre/maplibre-native/issues/165" } }, "expression": { @@ -2092,12 +2257,12 @@ ] } ], - "doc": "To increase the chance of placing high-priority labels on the map, you can provide an array of `text-anchor` locations, each paired with an offset value. The renderer will attempt to place the label at each location, in order, before moving on to the next location+offset. Use `text-justify: auto` to choose justification based on anchor position. \n\n The length of the array must be even, and must alternate between enum and point entries. i.e., each anchor location must be accompanied by a point, and that point defines the offset when the corresponding anchor location is used. Positive offset values indicate right and down, while negative values indicate left and up. Anchor locations may repeat, allowing the renderer to try multiple offsets to try and place a label using the same anchor. \n\n When present, this property takes precedence over `text-anchor`, `text-variable-anchor`, `text-offset`, and `text-radial-offset`. \n\n ```json \n { \"text-variable-anchor-offset\": [\"top\", [0, 4], \"left\", [3,0], \"bottom\", [1, 1]] } \n ``` \n\n When the renderer chooses the `top` anchor, `[0, 4]` will be used for `text-offset`; the text will be shifted down by 4 ems. \n\n When the renderer chooses the `left` anchor, `[3, 0]` will be used for `text-offset`; the text will be shifted right by 3 ems.", + "doc": "To increase the chance of placing high-priority labels on the map, you can provide an array of `text-anchor` locations, each paired with an offset value. The renderer will attempt to place the label at each location, in order, before moving on to the next location+offset. Use `text-justify: auto` to choose justification based on anchor position. \n\n The length of the array must be even, and must alternate between enum and point entries. i.e., each anchor location must be accompanied by a point, and that point defines the offset when the corresponding anchor location is used. Positive offset values indicate right and down, while negative values indicate left and up. Anchor locations may repeat, allowing the renderer to try multiple offsets to try and place a label using the same anchor. \n\n When present, this property takes precedence over `text-anchor`, `text-variable-anchor`, `text-offset`, and `text-radial-offset`. \n\n ```json \n\n { \"text-variable-anchor-offset\": [\"top\", [0, 4], \"left\", [3,0], \"bottom\", [1, 1]] } \n\n ``` \n\n When the renderer chooses the `top` anchor, `[0, 4]` will be used for `text-offset`; the text will be shifted down by 4 ems. \n\n When the renderer chooses the `left` anchor, `[3, 0]` will be used for `text-offset`; the text will be shifted right by 3 ems.", "sdk-support": { "basic functionality": { "js": "3.3.0", - "ios": "https://github.com/maplibre/maplibre-native/issues/2358", - "android": "https://github.com/maplibre/maplibre-native/issues/2358" + "ios": "6.8.0", + "android": "11.6.0" }, "data-driven styling": { "js": "3.3.0", @@ -2569,6 +2734,26 @@ "property-type": "constant" } }, + "layout_color-relief": { + "visibility": { + "type": "enum", + "values": { + "visible": { + "doc": "The layer is shown." + }, + "none": { + "doc": "The layer is not shown." + } + }, + "default": "visible", + "doc": "Whether this layer is displayed.", + "sdk-support": { + "basic functionality": { + } + }, + "property-type": "constant" + } + }, "filter": { "type": "array", "value": "*", @@ -2693,7 +2878,7 @@ "default": { "type": "*", "required": false, - "doc": "A value to serve as a fallback function result when a value isn't otherwise available. It is used in the following circumstances:\n* In categorical functions, when the feature value does not match any of the stop domain values.\n* In property and zoom-and-property functions, when a feature does not contain a value for the specified property.\n* In identity functions, when the feature value is not valid for the style property (for example, if the function is being used for a `circle-color` property but the feature property value is not a string or not a valid color).\n* In interval or exponential property and zoom-and-property functions, when the feature value is not numeric.\nIf no default is provided, the style property's default is used in these circumstances." + "doc": "A value to serve as a fallback function result when a value isn't otherwise available. It is used in the following circumstances:\n\n* In categorical functions, when the feature value does not match any of the stop domain values.\n\n* In property and zoom-and-property functions, when a feature does not contain a value for the specified property.\n\n* In identity functions, when the feature value is not valid for the style property (for example, if the function is being used for a `circle-color` property but the feature property value is not a string or not a valid color).\n\n* In interval or exponential property and zoom-and-property functions, when the feature value is not numeric.\n\nIf no default is provided, the style property's default is used in these circumstances." } }, "function_stop": { @@ -2719,13 +2904,32 @@ "values": { "let": { "doc": "Binds expressions to named variables, which can then be referenced in the result expression using `[\"var\", \"variable_name\"]`.\n\n - [Visualize population density](https://maplibre.org/maplibre-gl-js/docs/examples/visualize-population-density/)", - "example": { - "syntax": { - "method": ["string", "value", "expression"], - "result": "value" - }, - "value": ["let", "someNumber", 500, ["interpolate", ["linear"], ["var", "someNumber"], 274, "#edf8e9", 1551, "#006d2c"]] + "syntax": { + "overloads": [ + { + "parameters": ["var_1_name", "var_1_value", "...", "var_n_name", "var_n_value", "expression"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "var_i_name", + "type": "string literal", + "description": "The name of the i-th variable." + }, + { + "name": "var_i_value", + "type": "any", + "description": "The value of the i-th variable." + }, + { + "name": "expression", + "type": "any", + "description": "The expression within which the named variables can be referenced." + } + ] }, + "example": ["let", "someNumber", 500, ["interpolate", ["linear"], ["var", "someNumber"], 274, "#edf8e9", 1551, "#006d2c"]], "group": "Variable binding", "sdk-support": { "basic functionality": { @@ -2737,13 +2941,22 @@ }, "var": { "doc": "References variable bound using `let`.\n\n - [Visualize population density](https://maplibre.org/maplibre-gl-js/docs/examples/visualize-population-density/)", - "example": { - "syntax": { - "method": ["string"], - "result": "value" - }, - "value": ["var", "density"] + "syntax": { + "overloads": [ + { + "parameters": ["var_name"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "var_name", + "type": "string literal", + "description": "The name of the variable bound using `let`." + } + ] }, + "example": ["var", "density"], "group": "Variable binding", "sdk-support": { "basic functionality": { @@ -2755,13 +2968,29 @@ }, "literal": { "doc": "Provides a literal array or object value.\n\n - [Display and style rich text labels](https://maplibre.org/maplibre-gl-js/docs/examples/display-and-style-rich-text-labels/)", - "example": { - "syntax": { - "method": ["JSON object or array"], - "result": "array | object" - }, - "value": ["literal",["DIN Offc Pro Italic", "Arial Unicode MS Regular"]] + "syntax": { + "overloads": [ + { + "parameters": ["json_object"], + "output-type": "object" + }, + { + "parameters": ["json_array"], + "output-type": "array" + } + ], + "parameters": [ + { + "name": "json_object", + "type": "JSON object" + }, + { + "name": "json_array", + "type": "JSON array" + } + ] }, + "example": ["literal", ["DIN Offc Pro Italic", "Arial Unicode MS Regular"]], "group": "Types", "sdk-support": { "basic functionality": { @@ -2772,14 +3001,40 @@ } }, "array": { - "doc": "Asserts that the input is an array (optionally with a specific item type and length). If, when the input expression is evaluated, it is not of the asserted type, then this assertion will cause the whole expression to be aborted.", - "example": { - "syntax": { - "method": ["value", "string?", "number?"], - "result": "array" - }, - "value": ["array", ["literal", ["a", "b", "c"]], "string", 3] + "doc": "Asserts that the input is an array (optionally with a specific item type and length). If, when the input expression is evaluated, it is not of the asserted type or length, then this assertion will cause the whole expression to be aborted.", + "syntax": { + "overloads": [ + { + "parameters": ["value"], + "output-type": "array" + }, + { + "parameters": ["type", "value"], + "output-type": "array" + }, + { + "parameters": ["type", "length", "value"], + "output-type": "array" + } + ], + "parameters": [ + { + "name": "value", + "type": "any" + }, + { + "name": "type", + "type": "\"string\" | \"number\" | \"boolean\"", + "description": "The asserted type of the input array." + }, + { + "name": "length", + "type": "number literal", + "description": "The asserted length of the input array." + } + ] }, + "example": ["array", "string", 3, ["literal", ["a", "b", "c"]]], "group": "Types", "sdk-support": { "basic functionality": { @@ -2791,13 +3046,27 @@ }, "at": { "doc": "Retrieves an item from an array.", - "example": { - "syntax": { - "method": ["value", "number"], - "result": "value" - }, - "value": ["at", ["literal", ["a", "b", "c"]], 1] + "syntax": { + "overloads": [ + { + "parameters": ["index", "array"], + "output-type": "T" + } + ], + "parameters": [ + { + "name": "index", + "type": "number", + "description": "The index into `array`." + }, + { + "name": "array", + "type": "array", + "description": "The array of items to retrieve the specified item from." + } + ] }, + "example": ["at", 1, ["literal", ["a", "b", "c"]]], "group": "Lookup", "sdk-support": { "basic functionality": { @@ -2808,14 +3077,42 @@ } }, "in": { - "doc": "Determines whether an item exists in an array or a substring exists in a string.\n\n - [Measure distances](https://maplibre.org/maplibre-gl-js/docs/examples/measure/)", - "example": { - "syntax": { - "method": ["value", "value"], - "result": "boolean" - }, - "value": ["in", "$type", "Point"] + "doc": "Determines whether an item exists in an array or a substring exists in a string.\n\n - [Measure distances](https://maplibre.org/maplibre-gl-js/docs/examples/measure-distances/)", + "syntax": { + "overloads": [ + { + "parameters": ["item", "array"], + "output-type": "boolean" + }, + { + "parameters": ["substring", "string"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "item", + "type": "T", + "description": "The needle to search for within `array`." + }, + { + "name": "array", + "type": "array", + "description": "The haystack through which to search for `item`." + }, + { + "name": "substring", + "type": "string", + "description": "The needle to search for within `string`." + }, + { + "name": "string", + "type": "string", + "description": "The haystack through which to search for `substring`." + } + ] }, + "example": ["in", "$type", "Point"], "group": "Lookup", "sdk-support": { "basic functionality": { @@ -2827,13 +3124,46 @@ }, "index-of": { "doc": "Returns the first position at which an item can be found in an array or a substring can be found in a string, or `-1` if the input cannot be found. Accepts an optional index from where to begin the search. In a string, a UTF-16 surrogate pair counts as a single position.", - "example": { - "syntax": { - "method": ["value", "value", "number?"], - "result": "number" - }, - "value": ["index-of", "foo", ["baz", "bar", "hello", "foo", "world"]] + "syntax": { + "overloads": [ + { + "parameters": ["item", "array", "from_index?"], + "output-type": "number" + }, + { + "parameters": ["substring", "string", "from_index?"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "item", + "type": "T", + "description": "The needle to search for within `array`." + }, + { + "name": "array", + "type": "array", + "description": "The haystack through which to search for `item`." + }, + { + "name": "substring", + "type": "string", + "description": "The needle to search for within `string`." + }, + { + "name": "string", + "type": "string", + "description": "The haystack through which to search for `substring`." + }, + { + "name": "from_index", + "type": "number", + "description": "The index from where to begin the search." + } + ] }, + "example": ["index-of", "foo", ["baz", "bar", "hello", "foo", "world"]], "group": "Lookup", "sdk-support": { "basic functionality": { @@ -2844,14 +3174,42 @@ } }, "slice": { - "doc": "Returns an item from an array or a substring from a string from a specified start index, or between a start index and an end index if set. The return value is inclusive of the start index but not of the end index. In a string, a UTF-16 surrogate pair counts as a single position.", - "example": { - "syntax": { - "method": ["value", "number", "number?"], - "result": "value" - }, - "value": ["slice", ["get", "name"], 0, 3] + "doc": "Returns a subarray from an array or a substring from a string from a specified start index, or between a start index and an end index if set. The return value is inclusive of the start index but not of the end index. In a string, a UTF-16 surrogate pair counts as a single position.", + "syntax": { + "overloads": [ + { + "parameters": ["array", "start_index", "end_index?"], + "output-type": "array" + }, + { + "parameters": ["string", "start_index", "end_index?"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "array", + "type": "array", + "description": "The original array from which to extract the subarray." + }, + { + "name": "string", + "type": "string", + "description": "The original string from which to extract the substring." + }, + { + "name": "start_index", + "type": "number", + "description": "The inclusive index from which `slice` extracts items or characters from the subarray or substring." + }, + { + "name": "end_index", + "type": "number", + "description": "The non-inclusive index up to which `slice` extracts items or characters from the subarray or substring." + } + ] }, + "example": ["slice", ["get", "name"], 0, 3], "group": "Lookup", "sdk-support": { "basic functionality": { @@ -2862,14 +3220,31 @@ } }, "case": { - "doc": "Selects the first output whose corresponding test condition evaluates to true, or the fallback value otherwise.\n\n - [Create a hover effect](https://maplibre.org/maplibre-gl-js/docs/examples/hover-styles/)\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/cluster-html/)", - "example": { - "syntax": { - "method": ["value", "value", "...", "fallback: value"], - "result": "value" - }, - "value": ["case", ["boolean", ["feature-state", "hover"], false], 1, 0.5 ] + "doc": "Selects the first output whose corresponding test condition evaluates to true, or the fallback value otherwise.\n\n - [Create a hover effect](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-hover-effect/)\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/display-html-clusters-with-custom-properties/)", + "syntax": { + "overloads": [ + { + "parameters": ["condition_1", "output_1", "...", "condition_n", "output_n", "fallback"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "condition_i", + "type": "boolean" + }, + { + "name": "output_i", + "type": "any" + }, + { + "name": "fallback", + "type": "any", + "description": "The result when no condition evaluates to true." + } + ] }, + "example": ["case", ["boolean", ["feature-state", "hover"], false], 1, 0.5], "group": "Decision", "sdk-support": { "basic functionality": { @@ -2880,14 +3255,38 @@ } }, "match": { - "doc": "Selects the output whose label value matches the input value, or the fallback value if no match is found. The input can be any expression (e.g. `[\"get\", \"building_type\"]`). Each label must be either:\n - a single literal value; or\n - an array of literal values, whose values must be all strings or all numbers (e.g. `[100, 101]` or `[\"c\", \"b\"]`). The input matches if any of the values in the array matches, similar to the `\"in\"` operator.\nEach label must be unique. If the input type does not match the type of the labels, the result will be the fallback value.", - "example": { - "syntax": { - "method": ["value", "value", "...", "fallback: value"], - "result": "value" - }, - "value": ["match", ["get", "building_type"], "residential", "#f00", "commercial", "#0f0", "#000"] + "doc": "Selects the output whose label value matches the input value, or the fallback value if no match is found. The input can be any expression (e.g. `[\"get\", \"building_type\"]`). Each label must be either:\n\n - a single literal value; or\n\n - an array of literal values, whose values must be all strings or all numbers (e.g. `[100, 101]` or `[\"c\", \"b\"]`). The input matches if any of the values in the array matches, similar to the `\"in\"` operator.\n\nEach label must be unique. If the input type does not match the type of the labels, the result will be the fallback value.", + "syntax": { + "overloads": [ + { + "parameters": ["input", "label_1", "output_1", "...", "label_n", "output_n", "fallback"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "input", + "type": "string | number", + "description": "Any expression." + }, + { + "name": "label_i", + "type": "string literal | number literal | array | array", + "description": "The i-th literal value or array of literal values to match the input against." + }, + { + "name": "output_i", + "type": "any", + "description": "The result when the i-th label is the first label to match the input." + }, + { + "name": "fallback", + "type": "any", + "description": "The result when no label matches the input." + } + ] }, + "example": ["match", ["get", "building_type"], "residential", "#f00", "commercial", "#0f0", "#000"], "group": "Decision", "sdk-support": { "basic functionality": { @@ -2898,14 +3297,22 @@ } }, "coalesce": { - "doc": "Evaluates each expression in turn until the first non-null value is obtained, and returns that value.\n\n - [Use a fallback image](https://maplibre.org/maplibre-gl-js/docs/examples/fallback-image/)", - "example": { - "syntax": { - "method": ["coalesce", "value", "fallback"], - "result": "value" - }, - "value": ["coalesce", ["image", ["concat", ["get", "icon"], "_15"]], ["image", "marker_15"] ] + "doc": "Evaluates each expression in turn until the first non-null value is obtained, and returns that value.\n\n - [Use a fallback image](https://maplibre.org/maplibre-gl-js/docs/examples/use-a-fallback-image/)", + "syntax": { + "overloads": [ + { + "parameters": ["expression_1", "...", "expression_n"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "expression_i", + "type": "any" + } + ] }, + "example": ["coalesce", ["image", ["concat", ["get", "icon"], "_15"]], ["image", "marker_15"]], "group": "Decision", "sdk-support": { "basic functionality": { @@ -2916,14 +3323,38 @@ } }, "step": { - "doc": "Produces discrete, stepped results by evaluating a piecewise-constant function defined by pairs of input and output values (\"stops\"). The `input` may be any numeric expression (e.g., `[\"get\", \"population\"]`). Stop inputs must be numeric literals in strictly ascending order. Returns the output value of the stop just less than the input, or the first output if the input is less than the first stop.\n\n - [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/cluster/)", - "example": { - "syntax": { - "method": ["step", "number", "number", "number", "..."], - "result": "number" - }, - "value": [ "step", ["get", "point_count"], 20, 100, 30, 750, 40] + "doc": "Produces discrete, stepped results by evaluating a piecewise-constant function defined by pairs of input and output values (\"stops\"). The `input` may be any numeric expression (e.g., `[\"get\", \"population\"]`). Stop inputs must be numeric literals in strictly ascending order.\n\nReturns the output value of the stop just less than the input, or the first output if the input is less than the first stop.\n\n - [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/create-and-style-clusters/)", + "syntax": { + "overloads": [ + { + "parameters": ["input", "output_0", "stop_1_input", "stop_1_output", "...", "stop_n_input", "stop_n_output"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "input", + "type": "number", + "description": "Any numeric expression." + }, + { + "name": "output_0", + "type": "any", + "description": "The result when the `input` is less than the first stop." + }, + { + "name": "stop_i_input", + "type": "number literal", + "description": "The value of the i-th stop against which the `input` is compared." + }, + { + "name": "stop_i_output", + "type": "any", + "description": "The result when the i-th stop is the last stop less than the `input`." + } + ] }, + "example": ["step", ["get", "point_count"], 20, 100, 30, 750, 40], "group": "Ramps, scales, curves", "sdk-support": { "basic functionality": { @@ -2934,14 +3365,38 @@ } }, "interpolate": { - "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). The `input` may be any numeric expression (e.g., `[\"get\", \"population\"]`). Stop inputs must be numeric literals in strictly ascending order. The output type must be `number`, `array`, or `color`.\n\nInterpolation types:\n\n- `[\"linear\"]`, or an expression returning one of those types: Interpolates linearly between the pair of stops just less than and just greater than the input.\n- `[\"exponential\", base]`: Interpolates exponentially between the stops just less than and just greater than the input. `base` controls the rate at which the output increases: higher values make the output increase more towards the high end of the range. With values close to 1 the output increases linearly.\n- `[\"cubic-bezier\", x1, y1, x2, y2]`: Interpolates using the cubic bezier curve defined by the given control points.\n\n - [Animate map camera around a point](https://maplibre.org/maplibre-gl-js/docs/examples/animate-camera-around-point/)\n - [Change building color based on zoom level](https://maplibre.org/maplibre-gl-js/docs/examples/change-building-color-based-on-zoom-level/)\n - [Create a heatmap layer](https://maplibre.org/maplibre-gl-js/docs/examples/heatmap-layer/)\n - [Visualize population density](https://maplibre.org/maplibre-gl-js/docs/examples/visualize-population-density/)", - "example": { - "syntax": { - "method": ["[\"linear\"] | [\"exponential\", base] | [\"cubic-bezier\", x1, y1, x2, y2]", "number", "number", "number", "..."], - "result": "number | array | color" - }, - "value": ["interpolate", ["linear"], ["zoom"], 15, 0, 15.05, ["get", "height"]] + "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). The `input` may be any numeric expression (e.g., `[\"get\", \"population\"]`). Stop inputs must be numeric literals in strictly ascending order. The output type must be `number`, `array`, `color`, `array`, or `projection`.\n\nInterpolation types:\n\n- `[\"linear\"]`, or an expression returning one of those types: Interpolates linearly between the pair of stops just less than and just greater than the input.\n\n- `[\"exponential\", base]`: Interpolates exponentially between the stops just less than and just greater than the input. `base` controls the rate at which the output increases: higher values make the output increase more towards the high end of the range. With values close to 1 the output increases linearly.\n\n- `[\"cubic-bezier\", x1, y1, x2, y2]`: Interpolates using the cubic bezier curve defined by the given control points.\n\n - [Animate map camera around a point](https://maplibre.org/maplibre-gl-js/docs/examples/animate-camera-around-point/)\n\n - [Change building color based on zoom level](https://maplibre.org/maplibre-gl-js/docs/examples/change-building-color-based-on-zoom-level/)\n\n - [Create a heatmap layer](https://maplibre.org/maplibre-gl-js/docs/examples/heatmap-layer/)\n\n - [Visualize population density](https://maplibre.org/maplibre-gl-js/docs/examples/visualize-population-density/)", + "syntax": { + "overloads": [ + { + "parameters": ["interpolation_type", "input", "stop_1_input", "stop_1_output", "...", "stop_n_input", "stop_n_output"], + "output-type": "number | array | color | array | projection" + } + ], + "parameters": [ + { + "name": "interpolation_type", + "type": "[\"linear\"] | [\"exponential\", base] | [\"cubic-bezier\", x1, y1, x2, y2]", + "description": "The interpolation type." + }, + { + "name": "input", + "type": "number", + "description": "Any numeric expression." + }, + { + "name": "stop_i_input", + "type": "number literal", + "description": "The value of the i-th stop against which the `input` is compared." + }, + { + "name": "stop_i_output", + "type": "number | array | color | array | projection", + "description": "The output value corresponding to the i-th stop." + } + ] }, + "example": ["interpolate", ["linear"], ["zoom"], 15, 0, 15.05, ["get", "height"]], "group": "Ramps, scales, curves", "sdk-support": { "basic functionality": { @@ -2952,46 +3407,92 @@ } }, "interpolate-hcl": { - "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). Works like `interpolate`, but the output type must be `color`, and the interpolation is performed in the Hue-Chroma-Luminance color space.", - "example": { - "syntax": { - "method": ["[\"linear\"] | [\"exponential\", base] | [\"cubic-bezier\", x1, y1, x2, y2]", "number", "number", "number", "..."], - "result": "color" - }, - "value": ["interpolate-hcl", ["linear"], ["zoom"], 15, "#f00", 15.05, "#00f"] + "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). Works like `interpolate`, but the output type must be `color` or `array`, and the interpolation is performed in the Hue-Chroma-Luminance color space.", + "syntax": { + "overloads": [ + { + "parameters": ["interpolation_type", "input", "stop_1_input", "stop_1_output", "...", "stop_n_input", "stop_n_output"], + "output-type": "color | array" + } + ], + "parameters": [ + { + "name": "interpolation_type", + "type": "[\"linear\"] | [\"exponential\", base] | [\"cubic-bezier\", x1, y1, x2, y2]" + }, + { + "name": "input", + "type": "number" + }, + { + "name": "stop_i_input", + "type": "number literal" + }, + { + "name": "stop_i_output", + "type": "color | array" + } + ] }, + "example": ["interpolate-hcl", ["linear"], ["zoom"], 15, "#f00", 15.05, "#00f"], "group": "Ramps, scales, curves", "sdk-support": { "basic functionality": { - "js": "0.49.0" + "js": "0.49.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/2784", + "android": "https://github.com/maplibre/maplibre-native/issues/2784" } } }, "interpolate-lab": { - "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). Works like `interpolate`, but the output type must be `color`, and the interpolation is performed in the CIELAB color space.", - "example": { - "syntax": { - "method": ["[\"linear\"] | [\"exponential\", base] | [\"cubic-bezier\", x1, y1, x2, y2]", "number", "number", "number", "..."], - "result": "color" - }, - "value": ["interpolate-lab", ["linear"], ["zoom"], 15, "#f00", 15.05, "#00f"] + "doc": "Produces continuous, smooth results by interpolating between pairs of input and output values (\"stops\"). Works like `interpolate`, but the output type must be `color` or `array`, and the interpolation is performed in the CIELAB color space.", + "syntax": { + "overloads": [ + { + "parameters": ["interpolation_type", "input", "stop_1_input", "stop_1_output", "...", "stop_n_input", "stop_n_output"], + "output-type": "color | array" + } + ], + "parameters": [ + { + "name": "interpolation_type", + "type": "[\"linear\"] | [\"exponential\", base] | [\"cubic-bezier\", x1, y1, x2, y2]" + }, + { + "name": "input", + "type": "number" + }, + { + "name": "stop_i_input", + "type": "number literal" + }, + { + "name": "stop_i_output", + "type": "color | array" + } + ] }, + "example": ["interpolate-lab", ["linear"], ["zoom"], 15, "#f00", 15.05, "#00f"], "group": "Ramps, scales, curves", "sdk-support": { "basic functionality": { - "js": "0.49.0" + "js": "0.49.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/2784", + "android": "https://github.com/maplibre/maplibre-native/issues/2784" } } }, "ln2": { - "doc": "Returns mathematical constant ln(2).", - "example": { - "syntax": { - "method": [], - "result": "number" - }, - "value": ["ln2"] + "doc": "Returns the mathematical constant ln(2).", + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "number" + } + ] }, + "example": ["ln2"], "group": "Math", "sdk-support": { "basic functionality": { @@ -3003,13 +3504,15 @@ }, "pi": { "doc": "Returns the mathematical constant pi.", - "example": { - "syntax": { - "method": [], - "result": "number" - }, - "value": ["pi"] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "number" + } + ] }, + "example": ["pi"], "group": "Math", "sdk-support": { "basic functionality": { @@ -3021,13 +3524,15 @@ }, "e": { "doc": "Returns the mathematical constant e.", - "example": { - "syntax": { - "method": [], - "result": "number" - }, - "value": ["e"] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "number" + } + ] }, + "example": ["e"], "group": "Math", "sdk-support": { "basic functionality": { @@ -3039,13 +3544,21 @@ }, "typeof": { "doc": "Returns a string describing the type of the given value.", - "example": { - "syntax": { - "method": ["value"], - "result": "string" - }, - "value": ["typeof", ["get", "name"]] + "syntax": { + "overloads": [ + { + "parameters": ["value"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "value", + "type": "any" + } + ] }, + "example": ["typeof", ["get", "name"]], "group": "Types", "sdk-support": { "basic functionality": { @@ -3057,13 +3570,21 @@ }, "string": { "doc": "Asserts that the input value is a string. If multiple values are provided, each one is evaluated in order until a string is obtained. If none of the inputs are strings, the expression is an error.", - "example": { - "syntax": { - "method": ["value", "fallback: value", "fallback: value", "..."], - "result": "string" - }, - "value": ["string", ["get", "name"]] + "syntax": { + "overloads": [ + { + "parameters": ["value_1", "...", "value_n"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "value_i", + "type": "any" + } + ] }, + "example": ["string", ["get", "name"]], "group": "Types", "sdk-support": { "basic functionality": { @@ -3075,13 +3596,21 @@ }, "number": { "doc": "Asserts that the input value is a number. If multiple values are provided, each one is evaluated in order until a number is obtained. If none of the inputs are numbers, the expression is an error.", - "example": { - "syntax": { - "method": ["value", "fallback: value", "fallback: value", "..."], - "result": "number" - }, - "value": ["number", ["get", "population"]] + "syntax": { + "overloads": [ + { + "parameters": ["value_1", "...", "value_n"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "value_i", + "type": "any" + } + ] }, + "example": ["number", ["get", "population"]], "group": "Types", "sdk-support": { "basic functionality": { @@ -3092,14 +3621,22 @@ } }, "boolean": { - "doc": "Asserts that the input value is a boolean. If multiple values are provided, each one is evaluated in order until a boolean is obtained. If none of the inputs are booleans, the expression is an error.\n\n - [Create a hover effect](https://maplibre.org/maplibre-gl-js/docs/examples/hover-styles/)", - "example": { - "syntax": { - "method": ["value", "fallback: value", "fallback: value", "..."], - "result": "boolean" - }, - "value": ["boolean", ["feature-state", "hover"], false] + "doc": "Asserts that the input value is a boolean. If multiple values are provided, each one is evaluated in order until a boolean is obtained. If none of the inputs are booleans, the expression is an error.\n\n - [Create a hover effect](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-hover-effect/)", + "syntax": { + "overloads": [ + { + "parameters": ["value_1", "...", "value_n"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "value_i", + "type": "any" + } + ] }, + "example": ["boolean", ["feature-state", "hover"], false], "group": "Types", "sdk-support": { "basic functionality": { @@ -3111,13 +3648,21 @@ }, "object": { "doc": "Asserts that the input value is an object. If multiple values are provided, each one is evaluated in order until an object is obtained. If none of the inputs are objects, the expression is an error.", - "example": { - "syntax": { - "method": ["value", "fallback: value", "fallback: value", "..."], - "result": "object" - }, - "value": ["object", ["get", "some-property"]] + "syntax": { + "overloads": [ + { + "parameters": ["value_1", "...", "value_n"], + "output-type": "object" + } + ], + "parameters": [ + { + "name": "value_i", + "type": "any" + } + ] }, + "example": ["object", ["get", "some-property"]], "group": "Types", "sdk-support": { "basic functionality": { @@ -3129,13 +3674,21 @@ }, "collator": { "doc": "Returns a `collator` for use in locale-dependent comparison operations. The `case-sensitive` and `diacritic-sensitive` options default to `false`. The `locale` argument specifies the IETF language tag of the locale to use. If none is provided, the default locale is used. If the requested locale is not available, the `collator` will use a system-defined fallback locale. Use `resolved-locale` to test the results of locale fallback behavior.", - "example": { - "syntax": { - "method": ["collator", "{ \"case-sensitive\": boolean, \"diacritic-sensitive\": boolean, \"locale\": string }"], - "result": "collator" - }, - "value": ["collator", {"case-sensitive": true, "diacritic-sensitive": true, "locale": "fr"}] + "syntax": { + "overloads": [ + { + "parameters": ["options"], + "output-type": "collator" + } + ], + "parameters": [ + { + "name": "options", + "type": "{ \"case-sensitive\"?: boolean, \"diacritic-sensitive\"?: boolean, \"locale\"?: string }" + } + ] }, + "example": ["collator", {"case-sensitive": true, "diacritic-sensitive": true, "locale": "fr"}], "group": "Types", "sdk-support": { "basic functionality": { @@ -3146,14 +3699,26 @@ } }, "format": { - "doc": "Returns a `formatted` string for displaying mixed-format text in the `text-field` property. The input may contain a string literal or expression, including an [`'image'`](#image) expression. Strings may be followed by a style override object that supports the following properties:\n- `\"text-font\"`: Overrides the font stack specified by the root layout property.\n- `\"text-color\"`: Overrides the color specified by the root paint property.\n- `\"font-scale\"`: Applies a scaling factor on `text-size` as specified by the root layout property.\n\n - [Change the case of labels](https://maplibre.org/maplibre-gl-js/docs/examples/change-case-of-labels/)\n - [Display and style rich text labels](https://maplibre.org/maplibre-gl-js/docs/examples/display-and-style-rich-text-labels/)", - "example": { - "syntax": { - "method": ["value", "{ \"text-font\": string, \"text-color\": color, \"font-scale\": number }", "..."], - "result": "formatted" - }, - "value": ["format", ["upcase", ["get", "FacilityName"]], {"font-scale": 0.8}, "\n", {}, ["downcase", ["get", "Comments"]], {"font-scale": 0.6}] + "doc": "Returns a `formatted` string for displaying mixed-format text in the `text-field` property. The input may contain a string literal or expression, including an [`'image'`](#image) expression. Strings may be followed by a style override object that supports the following properties:\n\n- `\"text-font\"`: Overrides the font stack specified by the root layout property.\n\n- `\"text-color\"`: Overrides the color specified by the root paint property.\n\n- `\"font-scale\"`: Applies a scaling factor on `text-size` as specified by the root layout property.\n\n- `\"vertical-align\"`: Aligns vertically text section or image in relation to the row it belongs to. Possible values are: \n\t- `\"bottom\"` *default*: align the bottom of this section with the bottom of other sections.\n\"Visual\n\t- `\"center\"`: align the center of this section with the center of other sections.\n\"Visual\n\t- `\"top\"`: align the top of this section with the top of other sections.\n\"Visual\n\t- Refer to [the design proposal](https://github.com/maplibre/maplibre-style-spec/issues/832) for more details.\n\n - [Change the case of labels](https://maplibre.org/maplibre-gl-js/docs/examples/change-case-of-labels/)\n\n - [Display and style rich text labels](https://maplibre.org/maplibre-gl-js/docs/examples/display-and-style-rich-text-labels/)", + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "style_overrides_1?", "...", "input_n", "style_overrides_n?"], + "output-type": "formatted" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "string | image" + }, + { + "name": "style_overrides_i", + "type": "{ \"text-font\"?: string, \"text-color\"?: color, \"font-scale\"?: number, \"vertical-align\"?: \"bottom\" | \"center\" | \"top\" }" + } + ] }, + "example": ["format", ["upcase", ["get", "FacilityName"]], {"font-scale": 0.8}, "\n\n", {}, ["downcase", ["get", "Comments"]], {"font-scale": 0.6, "vertical-align": "center"}], "group": "Types", "sdk-support": { "basic functionality": { @@ -3176,6 +3741,11 @@ "android": "7.3.0", "ios": "4.10.0" }, + "vertical-align": { + "js": "5.1.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3055", + "ios": "https://github.com/maplibre/maplibre-native/issues/3055" + }, "image": { "js": "1.6.0", "android": "8.6.0", @@ -3184,14 +3754,22 @@ } }, "image": { - "doc": "Returns an `image` type for use in `icon-image`, `*-pattern` entries and as a section in the `format` expression. If set, the `image` argument will check that the requested image exists in the style and will return either the resolved image name or `null`, depending on whether or not the image is currently in the style. This validation process is synchronous and requires the image to have been added to the style before requesting it in the `image` argument.\n\n - [Use a fallback image](https://maplibre.org/maplibre-gl-js/docs/examples/fallback-image/)", - "example": { - "syntax": { - "method": ["value"], - "result": "image" - }, - "value": ["image", "marker_15"] + "doc": "Returns an `image` type for use in `icon-image`, `*-pattern` entries and as a section in the `format` expression. If set, the `image` argument will check that the requested image exists in the style and will return either the resolved image name or `null`, depending on whether or not the image is currently in the style. This validation process is synchronous and requires the image to have been added to the style before requesting it in the `image` argument.\n\n - [Use a fallback image](https://maplibre.org/maplibre-gl-js/docs/examples/use-a-fallback-image/)", + "syntax": { + "overloads": [ + { + "parameters": ["image_name"], + "output-type": "image" + } + ], + "parameters": [ + { + "name": "image_name", + "type": "string" + } + ] }, + "example": ["image", "marker_15"], "group": "Types", "sdk-support": { "basic functionality": { @@ -3202,30 +3780,52 @@ } }, "number-format": { - "doc": "Converts the input number into a string representation using the providing formatting rules. If set, the `locale` argument specifies the locale to use, as a BCP 47 language tag. If set, the `currency` argument specifies an ISO 4217 code to use for currency-style formatting. If set, the `min-fraction-digits` and `max-fraction-digits` arguments specify the minimum and maximum number of fractional digits to include.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/cluster-html/)", - "example": { - "syntax": { - "method": ["number", "{ \"locale\": string, \"currency\": string, \"min-fraction-digits\": number, \"max-fraction-digits\": number }"], - "result": "string" - }, - "value": ["number-format", ["get", "mag"], {"min-fraction-digits": 1, "max-fraction-digits": 1}] + "doc": "Converts the input number into a string representation using the providing formatting rules. If set, the `locale` argument specifies the locale to use, as a BCP 47 language tag. If set, the `currency` argument specifies an ISO 4217 code to use for currency-style formatting. If set, the `min-fraction-digits` and `max-fraction-digits` arguments specify the minimum and maximum number of fractional digits to include.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/display-html-clusters-with-custom-properties/)", + "syntax": { + "overloads": [ + { + "parameters": ["input", "format_options"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + }, + { + "name": "format_options", + "type": "{ \"locale\"?: string, \"currency\"?: string, \"min-fraction-digits\"?: number, \"max-fraction-digits\"?: number }" + } + ] }, + "example": ["number-format", ["get", "mag"], {"min-fraction-digits": 1, "max-fraction-digits": 1}], "group": "Types", "sdk-support": { "basic functionality": { - "js": "0.54.0" + "js": "0.54.0", + "android": "8.4.0", + "ios": "supported" } } }, "to-string": { - "doc": "Converts the input value to a string. If the input is `null`, the result is `\"\"`. If the input is a boolean, the result is `\"true\"` or `\"false\"`. If the input is a number, it is converted to a string as specified by the [\"NumberToString\" algorithm](https://tc39.github.io/ecma262/#sec-tostring-applied-to-the-number-type) of the ECMAScript Language Specification. If the input is a color, it is converted to a string of the form `\"rgba(r,g,b,a)\"`, where `r`, `g`, and `b` are numerals ranging from 0 to 255, and `a` ranges from 0 to 1. Otherwise, the input is converted to a string in the format specified by the [`JSON.stringify`](https://tc39.github.io/ecma262/#sec-json.stringify) function of the ECMAScript Language Specification.\n\n - [Create a time slider](https://maplibre.org/maplibre-gl-js/docs/examples/timeline-animation/)", - "example": { - "syntax": { - "method": ["value"], - "result": "string" - }, - "value": ["to-string", ["get", "mag"]] + "doc": "Converts the input value to a string. If the input is `null`, the result is `\"\"`. If the input is a boolean, the result is `\"true\"` or `\"false\"`. If the input is a number, it is converted to a string as specified by the [\"NumberToString\" algorithm](https://tc39.github.io/ecma262/#sec-tostring-applied-to-the-number-type) of the ECMAScript Language Specification. If the input is a color, it is converted to a string of the form `\"rgba(r,g,b,a)\"`, where `r`, `g`, and `b` are numerals ranging from 0 to 255, and `a` ranges from 0 to 1. Otherwise, the input is converted to a string in the format specified by the [`JSON.stringify`](https://tc39.github.io/ecma262/#sec-json.stringify) function of the ECMAScript Language Specification.\n\n - [Create a time slider](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-time-slider/)", + "syntax": { + "overloads": [ + { + "parameters": ["value"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "value", + "type": "any" + } + ] }, + "example": ["to-string", ["get", "mag"]], "group": "Types", "sdk-support": { "basic functionality": { @@ -3237,13 +3837,21 @@ }, "to-number": { "doc": "Converts the input value to a number, if possible. If the input is `null` or `false`, the result is 0. If the input is `true`, the result is 1. If the input is a string, it is converted to a number as specified by the [\"ToNumber Applied to the String Type\" algorithm](https://tc39.github.io/ecma262/#sec-tonumber-applied-to-the-string-type) of the ECMAScript Language Specification. If multiple values are provided, each one is evaluated in order until the first successful conversion is obtained. If none of the inputs can be converted, the expression is an error.", - "example": { - "syntax": { - "method": ["value", "fallback: value", "fallback: value", "..."], - "result": "number" - }, - "value": ["to-number", "someProperty"] + "syntax": { + "overloads": [ + { + "parameters": ["value_1", "...", "value_n"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "value_i", + "type": "any" + } + ] }, + "example": ["to-number", "someProperty"], "group": "Types", "sdk-support": { "basic functionality": { @@ -3254,14 +3862,22 @@ } }, "to-boolean": { - "doc": "Converts the input value to a boolean. The result is `false` when then input is an empty string, 0, `false`, `null`, or `NaN`; otherwise it is `true`.", - "example": { - "syntax": { - "method": ["value"], - "result": "boolean" - }, - "value": ["to-boolean", "someProperty"] + "doc": "Converts the input value to a boolean. The result is `false` when the input is an empty string, 0, `false`, `null`, or `NaN`; otherwise it is `true`.", + "syntax": { + "overloads": [ + { + "parameters": ["value"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "value", + "type": "any" + } + ] }, + "example": ["to-boolean", "someProperty"], "group": "Types", "sdk-support": { "basic functionality": { @@ -3273,13 +3889,21 @@ }, "to-rgba": { "doc": "Returns a four-element array containing the input color's red, green, blue, and alpha components, in that order.", - "example": { - "syntax": { - "method": ["color"], - "result": "array" - }, - "value": ["to-rgba", "#ff0000"] + "syntax": { + "overloads": [ + { + "parameters": ["color"], + "output-type": "array" + } + ], + "parameters": [ + { + "name": "color", + "type": "color" + } + ] }, + "example": ["to-rgba", "#ff0000"], "group": "Color", "sdk-support": { "basic functionality": { @@ -3291,13 +3915,21 @@ }, "to-color": { "doc": "Converts the input value to a color. If multiple values are provided, each one is evaluated in order until the first successful conversion is obtained. If none of the inputs can be converted, the expression is an error.\n\n - [Visualize population density](https://maplibre.org/maplibre-gl-js/docs/examples/visualize-population-density/)", - "example": { - "syntax": { - "method": ["value", "fallback: value", "fallback: value", "..."], - "result": "color" - }, - "value": ["to-color", "#edf8e9"] + "syntax": { + "overloads": [ + { + "parameters": ["value_1", "...", "value_n"], + "output-type": "color" + } + ], + "parameters": [ + { + "name": "value_i", + "type": "any" + } + ] }, + "example": ["to-color", "#edf8e9"], "group": "Types", "sdk-support": { "basic functionality": { @@ -3309,13 +3941,29 @@ }, "rgb": { "doc": "Creates a color value from red, green, and blue components, which must range between 0 and 255, and an alpha component of 1. If any component is out of range, the expression is an error.", - "example": { - "syntax": { - "method": ["number", "number", "number"], - "result": "color" - }, - "value": ["rgb", 255, 0, 0] + "syntax": { + "overloads": [ + { + "parameters": ["red", "green", "blue"], + "output-type": "color" + } + ], + "parameters": [ + { + "name": "red", + "type": "number" + }, + { + "name": "green", + "type": "number" + }, + { + "name": "blue", + "type": "number" + } + ] }, + "example": ["rgb", 255, 0, 0], "group": "Color", "sdk-support": { "basic functionality": { @@ -3327,13 +3975,33 @@ }, "rgba": { "doc": "Creates a color value from red, green, blue components, which must range between 0 and 255, and an alpha component which must range between 0 and 1. If any component is out of range, the expression is an error.", - "example": { - "syntax": { - "method": ["number", "number", "number", "number"], - "result": "color" - }, - "value": ["rgba", 255, 0, 0, 1] + "syntax": { + "overloads": [ + { + "parameters": ["red", "green", "blue", "alpha"], + "output-type": "color" + } + ], + "parameters": [ + { + "name": "red", + "type": "number" + }, + { + "name": "green", + "type": "number" + }, + { + "name": "blue", + "type": "number" + }, + { + "name": "alpha", + "type": "number" + } + ] }, + "example": ["rgba", 255, 0, 0, 1], "group": "Color", "sdk-support": { "basic functionality": { @@ -3344,14 +4012,28 @@ } }, "get": { - "doc": "Retrieves a property value from the current feature's properties, or from another object if a second argument is provided. Returns null if the requested property is missing.\n\n - [Change the case of labels](https://maplibre.org/maplibre-gl-js/docs/examples/change-case-of-labels/)\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/cluster-html/)\n - [Extrude polygons for 3D indoor mapping](https://maplibre.org/maplibre-gl-js/docs/examples/3d-extrusion-floorplan/)", - "example": { - "syntax": { - "method": ["string"], - "result": "value" - }, - "value": ["get", "someProperty"] + "doc": "Retrieves a property value from the current feature's properties, or from another object if a second argument is provided. Returns null if the requested property is missing.\n\n - [Change the case of labels](https://maplibre.org/maplibre-gl-js/docs/examples/change-case-of-labels/)\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/display-html-clusters-with-custom-properties/)\n\n - [Extrude polygons for 3D indoor mapping](https://maplibre.org/maplibre-gl-js/docs/examples/extrude-polygons-for-3d-indoor-mapping/)", + "syntax": { + "overloads": [ + { + "parameters": ["property_name", "object?"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "property_name", + "type": "string", + "description": "The name of the property to retrieve the value of." + }, + { + "name": "object", + "type": "object", + "description": "The object to retrieve the value from." + } + ] }, + "example": ["get", "someProperty"], "group": "Lookup", "sdk-support": { "basic functionality": { @@ -3362,14 +4044,28 @@ } }, "has": { - "doc": "Tests for the presence of an property value in the current feature's properties, or from another object if a second argument is provided.\n\n - [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/cluster/)", - "example": { - "syntax": { - "method": ["string"], - "result": "boolean" - }, - "value": ["has", "someProperty"] + "doc": "Tests for the presence of a property value in the current feature's properties, or from another object if a second argument is provided.\n\n - [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/create-and-style-clusters/)", + "syntax": { + "overloads": [ + { + "parameters": ["property_name", "object?"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "property_name", + "type": "string", + "description": "The name of the property to test for the presence of." + }, + { + "name": "object", + "type": "object", + "description": "The object in which to test for the presence of the `property_name` property." + } + ] }, + "example": ["has", "someProperty"], "group": "Lookup", "sdk-support": { "basic functionality": { @@ -3381,13 +4077,21 @@ }, "length": { "doc": "Gets the length of an array or string. In a string, a UTF-16 surrogate pair counts as a single position.", - "example": { - "syntax": { - "method": ["array"], - "result": "number" - }, - "value": ["length", ["get", "myArray"]] + "syntax": { + "overloads": [ + { + "parameters": ["array_or_string"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "array_or_string", + "type": "array | string" + } + ] }, + "example": ["length", ["get", "myArray"]], "group": "Lookup", "sdk-support": { "basic functionality": { @@ -3399,13 +4103,15 @@ }, "properties": { "doc": "Gets the feature properties object. Note that in some cases, it may be more efficient to use [\"get\", \"property_name\"] directly.", - "example": { - "syntax": { - "method": [], - "result": "value" - }, - "value": ["properties"] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "object" + } + ] }, + "example": ["properties"], "group": "Feature data", "sdk-support": { "basic functionality": { @@ -3416,30 +4122,42 @@ } }, "feature-state": { - "doc": "Retrieves a property value from the current feature's state. Returns null if the requested property is not present on the feature's state. A feature's state is not part of the GeoJSON or vector tile data, and must be set programmatically on each feature. When `source.promoteId` is not provided, features are identified by their `id` attribute, which must be an integer or a string that can be cast to an integer. When `source.promoteId` is provided, features are identified by their `promoteId` property, which may be a number, string, or any primitive data type. Note that [\"feature-state\"] can only be used with paint properties that support data-driven styling.\n\n - [Create a hover effect](https://maplibre.org/maplibre-gl-js/docs/examples/hover-styles/)", - "example": { - "syntax": { - "method": ["string"], - "result": "value" - }, - "value": ["feature-state", "hover"] + "doc": "Retrieves a property value from the current feature's state. Returns null if the requested property is not present on the feature's state. A feature's state is not part of the GeoJSON or vector tile data, and must be set programmatically on each feature. When `source.promoteId` is not provided, features are identified by their `id` attribute, which must be an integer or a string that can be cast to an integer. When `source.promoteId` is provided, features are identified by their `promoteId` property, which may be a number, string, or any primitive data type. Note that [\"feature-state\"] can only be used with paint properties that support data-driven styling.\n\n - [Create a hover effect](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-hover-effect/)", + "syntax": { + "overloads": [ + { + "parameters": ["property_name"], + "output-type": "any" + } + ], + "parameters": [ + { + "name": "property_name", + "type": "string" + } + ] }, + "example": ["feature-state", "hover"], "group": "Feature data", "sdk-support": { "basic functionality": { - "js": "0.46.0" + "js": "0.46.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/1698", + "android": "https://github.com/maplibre/maplibre-native/issues/1698" } } }, "geometry-type": { - "doc": "Gets the feature's geometry type: `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`.", - "example": { - "syntax": { - "method": [], - "result": "string" - }, - "value": ["==", ["geometry-type"], "Polygon"] + "doc": "Returns the feature's simple geometry type: `Point`, `LineString`, or `Polygon`. `MultiPoint`, `MultiLineString`, and `MultiPolygon` are returned as `Point`, `LineString`, and `Polygon`, respectively.", + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "string" + } + ] }, + "example": ["==", ["geometry-type"], "Polygon"], "group": "Feature data", "sdk-support": { "basic functionality": { @@ -3451,13 +4169,15 @@ }, "id": { "doc": "Gets the feature's id, if it has one.", - "example": { - "syntax": { - "method": [], - "result": "value" - }, - "value": ["id"] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "any" + } + ] }, + "example": ["id"], "group": "Feature data", "sdk-support": { "basic functionality": { @@ -3469,13 +4189,15 @@ }, "zoom": { "doc": "Gets the current zoom level. Note that in style layout and paint properties, [\"zoom\"] may only appear as the input to a top-level \"step\" or \"interpolate\" expression.", - "example": { - "syntax": { - "method": [], - "result": "number" - }, - "value": ["interpolate",["linear"],["zoom"],15,0,15.05,["get","height"]] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "number" + } + ] }, + "example": ["interpolate", ["linear"], ["zoom"], 15, 0, 15.05, ["get", "height"]], "group": "Zoom", "sdk-support": { "basic functionality": { @@ -3487,13 +4209,15 @@ }, "heatmap-density": { "doc": "Gets the kernel density estimation of a pixel in a heatmap layer, which is a relative measure of how many data points are crowded around a particular pixel. Can only be used in the `heatmap-color` property.", - "example": { - "syntax": { - "method": [], - "result": "number" - }, - "value": ["heatmap-density"] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "number" + } + ] }, + "example": ["heatmap-density"], "group": "Heatmap", "sdk-support": { "basic functionality": { @@ -3503,15 +4227,37 @@ } } }, + "elevation": { + "doc": "Gets the elevation of a pixel (in meters above the vertical datum reference of the `raster-dem` tiles) from a `raster-dem` source. Can only be used in the `color-relief-color` property of a `color-relief` layer.", + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "number" + } + ] + }, + "example": ["elevation"], + "group": "Color Relief", + "sdk-support": { + "basic functionality": { + "js": "5.6.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3408", + "ios": "https://github.com/maplibre/maplibre-native/issues/3408" + } + } + }, "line-progress": { "doc": "Gets the progress along a gradient line. Can only be used in the `line-gradient` property.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["line-progress", 0.5] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "number" + } + ] }, + "example": ["line-progress"], "group": "Feature data", "sdk-support": { "basic functionality": { @@ -3523,29 +4269,41 @@ }, "accumulated": { "doc": "Gets the value of a cluster property accumulated so far. Can only be used in the `clusterProperties` option of a clustered GeoJSON source.", - "example": { - "syntax": { - "method": ["string"], - "result": "value" - }, - "value": ["accumulated", "sum"] + "syntax": { + "overloads": [ + { + "parameters": [], + "output-type": "any" + } + ] }, + "example": ["accumulated"], "group": "Feature data", "sdk-support": { "basic functionality": { - "js": "0.53.0" + "js": "0.53.0", + "ios": "supported", + "android": "supported" } } }, "+": { "doc": "Returns the sum of the inputs.", - "example": { - "syntax": { - "method": ["number", "number", "..."], - "result": "number" - }, - "value": ["+", 2, 3] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "...", "input_n"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "number" + } + ] }, + "example": ["+", 2, 3], "group": "Math", "sdk-support": { "basic functionality": { @@ -3557,13 +4315,21 @@ }, "*": { "doc": "Returns the product of the inputs.", - "example": { - "syntax": { - "method": ["number", "number", "..."], - "result": "number" - }, - "value": ["*", 2, 3] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "...", "input_n"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "number" + } + ] }, + "example": ["*", 2, 3], "group": "Math", "sdk-support": { "basic functionality": { @@ -3575,13 +4341,36 @@ }, "-": { "doc": "For two inputs, returns the result of subtracting the second input from the first. For a single input, returns the result of subtracting it from 0.", - "example": { - "syntax": { - "method": ["number", "number?"], - "result": "number" - }, - "value": ["-", 10] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "input_2"], + "output-type": "number" + }, + { + "parameters": ["single_input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_1", + "type": "number", + "description": "The number from which to subtract `input_2`." + }, + { + "name": "input_2", + "type": "number", + "description": "The number to subtract from `input_1`." + }, + { + "name": "single_input", + "type": "number", + "description": "The number to subtract from 0." + } + ] }, + "example": ["-", 10], "group": "Math", "sdk-support": { "basic functionality": { @@ -3593,13 +4382,27 @@ }, "/": { "doc": "Returns the result of floating point division of the first input by the second.\n\n - [Visualize population density](https://maplibre.org/maplibre-gl-js/docs/examples/visualize-population-density/)", - "example": { - "syntax": { - "method": ["number", "number"], - "result": "number" - }, - "value": ["/", ["get", "population"], ["get", "sq-km"]] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "input_2"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_1", + "type": "number", + "description": "The dividend." + }, + { + "name": "input_2", + "type": "number", + "description": "The divisor." + } + ] }, + "example": ["/", ["get", "population"], ["get", "sq-km"]], "group": "Math", "sdk-support": { "basic functionality": { @@ -3611,13 +4414,27 @@ }, "%": { "doc": "Returns the remainder after integer division of the first input by the second.", - "example": { - "syntax": { - "method": ["number", "number"], - "result": "number" - }, - "value": ["%", 10, 3] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "input_2"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_1", + "type": "number", + "description": "The dividend." + }, + { + "name": "input_2", + "type": "number", + "description": "The divisor." + } + ] }, + "example": ["%", 10, 3], "group": "Math", "sdk-support": { "basic functionality": { @@ -3629,13 +4446,27 @@ }, "^": { "doc": "Returns the result of raising the first input to the power specified by the second.", - "example": { - "syntax": { - "method": ["number", "number"], - "result": "number" - }, - "value": ["^", 2, 3] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "input_2"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_1", + "type": "number", + "description": "The base." + }, + { + "name": "input_2", + "type": "number", + "description": "The exponent." + } + ] }, + "example": ["^", 2, 3], "group": "Math", "sdk-support": { "basic functionality": { @@ -3647,13 +4478,22 @@ }, "sqrt": { "doc": "Returns the square root of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["sqrt", 9] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number", + "description": "The radicand." + } + ] }, + "example": ["sqrt", 9], "group": "Math", "sdk-support": { "basic functionality": { @@ -3665,13 +4505,21 @@ }, "log10": { "doc": "Returns the base-ten logarithm of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["log10", 8] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["log10", 8], "group": "Math", "sdk-support": { "basic functionality": { @@ -3683,13 +4531,21 @@ }, "ln": { "doc": "Returns the natural logarithm of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["ln", 8] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["ln", 8], "group": "Math", "sdk-support": { "basic functionality": { @@ -3701,13 +4557,21 @@ }, "log2": { "doc": "Returns the base-two logarithm of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["log2", 8] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["log2", 8], "group": "Math", "sdk-support": { "basic functionality": { @@ -3719,13 +4583,21 @@ }, "sin": { "doc": "Returns the sine of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["sin", 1] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["sin", 1], "group": "Math", "sdk-support": { "basic functionality": { @@ -3737,13 +4609,21 @@ }, "cos": { "doc": "Returns the cosine of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["cos", 1] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["cos", 1], "group": "Math", "sdk-support": { "basic functionality": { @@ -3755,13 +4635,21 @@ }, "tan": { "doc": "Returns the tangent of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["tan", 1] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["tan", 1], "group": "Math", "sdk-support": { "basic functionality": { @@ -3773,13 +4661,21 @@ }, "asin": { "doc": "Returns the arcsine of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["asin", 1] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["asin", 1], "group": "Math", "sdk-support": { "basic functionality": { @@ -3791,13 +4687,21 @@ }, "acos": { "doc": "Returns the arccosine of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["acos", 1] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["acos", 1], "group": "Math", "sdk-support": { "basic functionality": { @@ -3809,13 +4713,21 @@ }, "atan": { "doc": "Returns the arctangent of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["atan", 1] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["atan", 1], "group": "Math", "sdk-support": { "basic functionality": { @@ -3827,13 +4739,21 @@ }, "min": { "doc": "Returns the minimum value of the inputs.", - "example": { - "syntax": { - "method": ["number", "number", "..."], - "result": "number" - }, - "value": ["min", 1, 2] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "...", "input_n"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "number" + } + ] }, + "example": ["min", 1, 2], "group": "Math", "sdk-support": { "basic functionality": { @@ -3845,13 +4765,21 @@ }, "max": { "doc": "Returns the maximum value of the inputs.", - "example": { - "syntax": { - "method": ["number", "number", "..."], - "result": "number" - }, - "value": ["max", 1, 2] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "...", "input_n"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "number" + } + ] }, + "example": ["max", 1, 2], "group": "Math", "sdk-support": { "basic functionality": { @@ -3863,13 +4791,21 @@ }, "round": { "doc": "Rounds the input to the nearest integer. Halfway values are rounded away from zero. For example, `[\"round\", -1.5]` evaluates to -2.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["round", 1.5] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["round", 1.5], "group": "Math", "sdk-support": { "basic functionality": { @@ -3881,13 +4817,21 @@ }, "abs": { "doc": "Returns the absolute value of the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["abs", -1.5] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["abs", -1.5], "group": "Math", "sdk-support": { "basic functionality": { @@ -3899,13 +4843,21 @@ }, "ceil": { "doc": "Returns the smallest integer that is greater than or equal to the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["ceil", 1.5] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["ceil", 1.5], "group": "Math", "sdk-support": { "basic functionality": { @@ -3917,13 +4869,21 @@ }, "floor": { "doc": "Returns the largest integer that is less than or equal to the input.", - "example": { - "syntax": { - "method": ["number"], - "result": "number" - }, - "value": ["floor", 1.5] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "input", + "type": "number" + } + ] }, + "example": ["floor", 1.5], "group": "Math", "sdk-support": { "basic functionality": { @@ -3935,13 +4895,21 @@ }, "distance": { "doc": "Returns the shortest distance in meters between the evaluated feature and the input geometry. The input value can be a valid GeoJSON of type `Point`, `MultiPoint`, `LineString`, `MultiLineString`, `Polygon`, `MultiPolygon`, `Feature`, or `FeatureCollection`. Distance values returned may vary in precision due to loss in precision from encoding geometries, particularly below zoom level 13.", - "example": { - "syntax": { - "method": ["GeoJSON geometry"], - "result": "number" - }, - "value": ["distance", { "type": "Point", "coordinates": [0, 0]}] + "syntax": { + "overloads": [ + { + "parameters": ["geojson"], + "output-type": "number" + } + ], + "parameters": [ + { + "name": "geojson", + "type": "GeoJSON object" + } + ] }, + "example": ["distance", {"type": "Point", "coordinates": [0, 0]}], "group": "Math", "sdk-support": { "basic functionality": { @@ -3952,14 +4920,31 @@ } }, "==": { - "doc": "Returns `true` if the input values are equal, `false` otherwise. The comparison is strictly typed: values of different runtime types are always considered unequal. Cases where the types are known to be different at parse time are considered invalid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Add multiple geometries from one GeoJSON source](https://maplibre.org/maplibre-gl-js/docs/examples/multiple-geometries/)\n - [Create a time slider](https://maplibre.org/maplibre-gl-js/docs/examples/timeline-animation/)\n - [Display buildings in 3D](https://maplibre.org/maplibre-gl-js/docs/examples/3d-buildings/)\n - [Filter symbols by toggling a list](https://maplibre.org/maplibre-gl-js/docs/examples/filter-markers/)", - "example": { - "syntax": { - "method": ["value", "value", "collator?"], - "result": "boolean" - }, - "value": ["==", "$type", "Polygon"] + "doc": "Returns `true` if the input values are equal, `false` otherwise. The comparison is strictly typed: values of different runtime types are always considered unequal. Cases where the types are known to be different at parse time are considered invalid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Add multiple geometries from one GeoJSON source](https://maplibre.org/maplibre-gl-js/docs/examples/multiple-geometries/)\n\n - [Create a time slider](https://maplibre.org/maplibre-gl-js/docs/examples/timeline-animation/)\n\n - [Display buildings in 3D](https://maplibre.org/maplibre-gl-js/docs/examples/display-buildings-in-3d/)\n\n - [Filter symbols by toggling a list](https://maplibre.org/maplibre-gl-js/docs/examples/filter-symbols-by-toggling-a-list/)", + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "input_2", "collator?"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "input_1", + "type": "any" + }, + { + "name": "input_2", + "type": "any" + }, + { + "name": "collator", + "type": "collator", + "description": "Options for locale-dependent comparison." + } + ] }, + "example": ["==", "$type", "Polygon"], "group": "Decision", "sdk-support": { "basic functionality": { @@ -3975,14 +4960,31 @@ } }, "!=": { - "doc": "Returns `true` if the input values are not equal, `false` otherwise. The comparison is strictly typed: values of different runtime types are always considered unequal. Cases where the types are known to be different at parse time are considered invalid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/cluster-html/)", - "example": { - "syntax": { - "method": ["value", "value", "collator?"], - "result": "boolean" - }, - "value": ["!=", "cluster", true] + "doc": "Returns `true` if the input values are not equal, `false` otherwise. The comparison is strictly typed: values of different runtime types are always considered unequal. Cases where the types are known to be different at parse time are considered invalid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/display-html-clusters-with-custom-properties/)", + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "input_2", "collator?"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "input_1", + "type": "any" + }, + { + "name": "input_2", + "type": "any" + }, + { + "name": "collator", + "type": "collator", + "description": "Options for locale-dependent comparison." + } + ] }, + "example": ["!=", "cluster", true], "group": "Decision", "sdk-support": { "basic functionality": { @@ -3999,13 +5001,34 @@ }, ">": { "doc": "Returns `true` if the first input is strictly greater than the second, `false` otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered in valid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.", - "example": { - "syntax": { - "method": ["value", "value", "collator?"], - "result": "boolean" + "syntax": { + "overloads": [ + { + "parameters": ["string_1", "string_2", "collator?"], + "output-type": "boolean" }, - "value": [">", ["get", "mag"], 2] + { + "parameters": ["number_1", "number_2", "collator?"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "string_i", + "type": "string" + }, + { + "name": "number_i", + "type": "number" + }, + { + "name": "collator", + "type": "collator", + "description": "Options for locale-dependent comparison." + } + ] }, + "example": [">", ["get", "mag"], 2], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4021,14 +5044,35 @@ } }, "<": { - "doc": "Returns `true` if the first input is strictly less than the second, `false` otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered in valid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/cluster-html/)", - "example": { - "syntax": { - "method": ["value", "value", "collator?"], - "result": "boolean" - }, - "value": ["<", ["get", "mag"], 2] + "doc": "Returns `true` if the first input is strictly less than the second, `false` otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered in valid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/display-html-clusters-with-custom-properties/)", + "syntax": { + "overloads": [ + { + "parameters": ["string_1", "string_2", "collator?"], + "output-type": "boolean" + }, + { + "parameters": ["number_1", "number_2", "collator?"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "string_i", + "type": "string" + }, + { + "name": "number_i", + "type": "number" + }, + { + "name": "collator", + "type": "collator", + "description": "Options for locale-dependent comparison." + } + ] }, + "example": ["<", ["get", "mag"], 2], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4044,14 +5088,35 @@ } }, ">=": { - "doc": "Returns `true` if the first input is greater than or equal to the second, `false` otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered in valid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/cluster-html/)", - "example": { - "syntax": { - "method": ["value", "value", "collator?"], - "result": "boolean" - }, - "value": [">=", ["get", "mag"], 6] + "doc": "Returns `true` if the first input is greater than or equal to the second, `false` otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered in valid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/display-html-clusters-with-custom-properties/)", + "syntax": { + "overloads": [ + { + "parameters": ["string_1", "string_2", "collator?"], + "output-type": "boolean" + }, + { + "parameters": ["number_1", "number_2", "collator?"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "string_i", + "type": "string" + }, + { + "name": "number_i", + "type": "number" + }, + { + "name": "collator", + "type": "collator", + "description": "Options for locale-dependent comparison." + } + ] }, + "example": [">=", ["get", "mag"], 6], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4068,13 +5133,34 @@ }, "<=": { "doc": "Returns `true` if the first input is less than or equal to the second, `false` otherwise. The arguments are required to be either both strings or both numbers; if during evaluation they are not, expression evaluation produces an error. Cases where this constraint is known not to hold at parse time are considered in valid and will produce a parse error. Accepts an optional `collator` argument to control locale-dependent string comparisons.", - "example": { - "syntax": { - "method": ["value", "value", "collator?"], - "result": "boolean" + "syntax": { + "overloads": [ + { + "parameters": ["string_1", "string_2", "collator?"], + "output-type": "boolean" }, - "value": ["<=", ["get", "mag"], 6] + { + "parameters": ["number_1", "number_2", "collator?"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "string_i", + "type": "string" + }, + { + "name": "number_i", + "type": "number" + }, + { + "name": "collator", + "type": "collator", + "description": "Options for locale-dependent comparison." + } + ] }, + "example": ["<=", ["get", "mag"], 6], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4090,14 +5176,22 @@ } }, "all": { - "doc": "Returns `true` if all the inputs are `true`, `false` otherwise. The inputs are evaluated in order, and evaluation is short-circuiting: once an input expression evaluates to `false`, the result is `false` and no further input expressions are evaluated.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/cluster-html/)", - "example": { - "syntax": { - "method": ["boolean", "boolean", "..."], - "result": "boolean" - }, - "value": ["all", [">=", ["get", "mag"], 4], ["<", ["get", "mag"], 5]] + "doc": "Returns `true` if all the inputs are `true`, `false` otherwise. The inputs are evaluated in order, and evaluation is short-circuiting: once an input expression evaluates to `false`, the result is `false` and no further input expressions are evaluated.\n\n - [Display HTML clusters with custom properties](https://maplibre.org/maplibre-gl-js/docs/examples/display-html-clusters-with-custom-properties/)", + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "...", "input_n"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "boolean" + } + ] }, + "example": ["all", [">=", ["get", "mag"], 4], ["<", ["get", "mag"], 5]], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4109,13 +5203,21 @@ }, "any": { "doc": "Returns `true` if any of the inputs are `true`, `false` otherwise. The inputs are evaluated in order, and evaluation is short-circuiting: once an input expression evaluates to `true`, the result is `true` and no further input expressions are evaluated.", - "example": { - "syntax": { - "method": ["boolean", "boolean", "..."], - "result": "boolean" - }, - "value": ["any", [">=", ["get", "mag"], 4], ["<", ["get", "mag"], 5]] + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "...", "input_n"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "boolean" + } + ] }, + "example": ["any", [">=", ["get", "mag"], 4], ["<", ["get", "mag"], 5]], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4126,14 +5228,22 @@ } }, "!": { - "doc": "Logical negation. Returns `true` if the input is `false`, and `false` if the input is `true`.\n\n - [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/cluster/)", - "example": { - "syntax": { - "method": ["value", "value", "collator?"], - "result": "boolean" - }, - "value": ["!", ["has", "point_count"]] + "doc": "Logical negation. Returns `true` if the input is `false`, and `false` if the input is `true`.\n\n - [Create and style clusters](https://maplibre.org/maplibre-gl-js/docs/examples/create-and-style-clusters/)", + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "input", + "type": "boolean" + } + ] }, + "example": ["!", ["has", "point_count"]], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4144,14 +5254,22 @@ } }, "within": { - "doc": "Returns `true` if the evaluated feature is fully contained inside a boundary of the input geometry, `false` otherwise. The input value can be a valid GeoJSON of type `Polygon`, `MultiPolygon`, `Feature`, or `FeatureCollection`. Supported features for evaluation:\n- `Point`: Returns `false` if a point is on the boundary or falls outside the boundary.\n- `LineString`: Returns `false` if any part of a line falls outside the boundary, the line intersects the boundary, or a line's endpoint is on the boundary.", - "example": { - "syntax": { - "method": ["GeoJSON geometry"], - "result": "boolean" - }, - "value": ["within", {"type": "Polygon","coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]}] + "doc": "Returns `true` if the evaluated feature is fully contained inside a boundary of the input geometry, `false` otherwise. The input value can be a valid GeoJSON of type `Polygon`, `MultiPolygon`, `Feature`, or `FeatureCollection`. Supported features for evaluation:\n\n- `Point`: Returns `false` if a point is on the boundary or falls outside the boundary.\n\n- `LineString`: Returns `false` if any part of a line falls outside the boundary, the line intersects the boundary, or a line's endpoint is on the boundary.", + "syntax": { + "overloads": [ + { + "parameters": ["geojson"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "geojson", + "type": "GeoJSON object" + } + ] }, + "example": ["within", {"type": "Polygon", "coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]}], "group": "Decision", "sdk-support": { "basic functionality": { @@ -4162,31 +5280,48 @@ } }, "is-supported-script": { - "doc": "Returns `true` if the input string is expected to render legibly. Returns `false` if the input string contains sections that cannot be rendered without potential loss of meaning (e.g. Indic scripts that require complex text shaping, or right-to-left scripts if the the `mapbox-gl-rtl-text` plugin is not in use in MapLibre GL JS).", - "example": { - "syntax": { - "method": ["string"], - "result": "boolean" - }, - "value": ["is-supported-script", "दिल्ली"] + "doc": "Returns `true` if the input string is expected to render legibly. Returns `false` if the input string contains sections that cannot be rendered without potential loss of meaning (e.g. Indic scripts that require complex text shaping, or right-to-left scripts if the `mapbox-gl-rtl-text` plugin is not in use in MapLibre GL JS).", + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "boolean" + } + ], + "parameters": [ + { + "name": "input", + "type": "string" + } + ] }, + "example": ["is-supported-script", "दिल्ली"], "group": "String", "sdk-support": { "basic functionality": { "js": "0.45.0", - "android": "6.6.0" + "android": "6.6.0", + "ios": "supported" } } }, "upcase": { "doc": "Returns the input string converted to uppercase. Follows the Unicode Default Case Conversion algorithm and the locale-insensitive case mappings in the Unicode Character Database.\n\n - [Change the case of labels](https://maplibre.org/maplibre-gl-js/docs/examples/change-case-of-labels/)", - "example": { - "syntax": { - "method": ["string"], - "result": "string" - }, - "value": ["upcase", ["get", "name"]] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "input", + "type": "string" + } + ] }, + "example": ["upcase", ["get", "name"]], "group": "String", "sdk-support": { "basic functionality": { @@ -4198,13 +5333,21 @@ }, "downcase": { "doc": "Returns the input string converted to lowercase. Follows the Unicode Default Case Conversion algorithm and the locale-insensitive case mappings in the Unicode Character Database.\n\n - [Change the case of labels](https://maplibre.org/maplibre-gl-js/docs/examples/change-case-of-labels/)", - "example": { - "syntax": { - "method": ["string"], - "result": "string" - }, - "value": ["downcase", ["get", "name"]] + "syntax": { + "overloads": [ + { + "parameters": ["input"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "input", + "type": "string" + } + ] }, + "example": ["downcase", ["get", "name"]], "group": "String", "sdk-support": { "basic functionality": { @@ -4215,14 +5358,22 @@ } }, "concat": { - "doc": "Returns a `string` consisting of the concatenation of the inputs. Each input is converted to a string as if by `to-string`.\n\n - [Add a generated icon to the map](https://maplibre.org/maplibre-gl-js/docs/examples/add-image-missing-generated/)\n - [Create a time slider](https://maplibre.org/maplibre-gl-js/docs/examples/timeline-animation/)\n - [Use a fallback image](https://maplibre.org/maplibre-gl-js/docs/examples/fallback-image/)\n - [Variable label placement](https://maplibre.org/maplibre-gl-js/docs/examples/variable-label-placement/)", - "example": { - "syntax": { - "method": ["string", "string", "..."], - "result": "string" - }, - "value": ["concat", "square-rgb-", ["get", "color"]] + "doc": "Returns a `string` consisting of the concatenation of the inputs. Each input is converted to a string as if by `to-string`.\n\n - [Add a generated icon to the map](https://maplibre.org/maplibre-gl-js/docs/examples/add-a-generated-icon-to-the-map/)\n\n - [Create a time slider](https://maplibre.org/maplibre-gl-js/docs/examples/create-a-time-slider/)\n\n - [Use a fallback image](https://maplibre.org/maplibre-gl-js/docs/examples/fallback-image/)\n\n - [Variable label placement](https://maplibre.org/maplibre-gl-js/docs/examples/variable-label-placement/)", + "syntax": { + "overloads": [ + { + "parameters": ["input_1", "...", "input_n"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "input_i", + "type": "any" + } + ] }, + "example": ["concat", "square-rgb-", ["get", "color"]], "group": "String", "sdk-support": { "basic functionality": { @@ -4234,16 +5385,24 @@ }, "resolved-locale": { "doc": "Returns the IETF language tag of the locale being used by the provided `collator`. This can be used to determine the default system locale, or to determine if a requested locale was successfully loaded.", - "example": { - "syntax": { - "method": ["collator"], - "result": "string" - }, - "value": ["resolved-locale", [ - "collator", - {"case-sensitive": true, "diacritic-sensitive": false, "locale": "de"} - ]] + "syntax": { + "overloads": [ + { + "parameters": ["collator"], + "output-type": "string" + } + ], + "parameters": [ + { + "name": "collator", + "type": "collator" + } + ] }, + "example": ["resolved-locale", [ + "collator", + {"case-sensitive": true, "diacritic-sensitive": false, "locale": "de"} + ]], "group": "String", "sdk-support": { "basic functionality": { @@ -4442,7 +5601,7 @@ ] }, "transition": true, - "doc": "How to blend the the sky color and the horizon color. Where 1 is blending the color at the middle of the sky and 0 is not blending at all and using the sky color only." + "doc": "How to blend the sky color and the horizon color. Where 1 is blending the color at the middle of the sky and 0 is not blending at all and using the sky color only." }, "atmosphere-blend": { "type": "number", @@ -4467,7 +5626,9 @@ "required": true, "sdk-support": { "basic functionality": { - "js": "2.2.0" + "js": "2.2.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/252", + "android": "https://github.com/maplibre/maplibre-native/issues/252" } } }, @@ -4478,23 +5639,25 @@ "default": 1.0, "sdk-support": { "basic functionality": { - "js": "2.2.0" + "js": "2.2.0", + "ios": "https://github.com/maplibre/maplibre-native/issues/252", + "android": "https://github.com/maplibre/maplibre-native/issues/252" } } } }, "projection": { "type": { - "type": "enum", - "doc": "The projection type.", + "type": "projectionDefinition", + "doc": "The projection definition type. Can be specified as a string, a transition state, or an expression.", "default": "mercator", - "values": { - "mercator": { - "doc": "The Mercator projection." - }, - "globe": { - "doc": "The globe projection." - } + "property-type": "data-constant", + "transition": false, + "expression": { + "interpolated": true, + "parameters": [ + "zoom" + ] } } }, @@ -4507,6 +5670,7 @@ "paint_symbol", "paint_raster", "paint_hillshade", + "paint_color-relief", "paint_background" ], "paint_fill": { @@ -4911,7 +6075,8 @@ "sdk-support": { "basic functionality": { "js": "0.50.0", - "ios": "4.7.0" + "ios": "4.7.0", + "android": "7.0.0" } }, "expression": { @@ -5795,7 +6960,7 @@ "minimum": 0, "transition": true, "units": "pixels", - "doc": "Distance of halo to the icon outline. \nThe unit is in pixels only for SDF sprites that were created with a blur radius of 8, multiplied by the display density. I.e., the radius needs to be 16 for `@2x` sprites, etc.", + "doc": "Distance of halo to the icon outline. \n\nThe unit is in pixels only for SDF sprites that were created with a blur radius of 8, multiplied by the display density. I.e., the radius needs to be 16 for `@2x` sprites, etc.", "requires": [ "icon-image" ], @@ -6316,17 +7481,49 @@ }, "paint_hillshade": { "hillshade-illumination-direction": { - "type": "number", + "type": "numberArray", "default": 335, "minimum": 0, "maximum": 359, - "doc": "The direction of the light source used to generate the hillshading with 0 as the top of the viewport if `hillshade-illumination-anchor` is set to `viewport` and due north if `hillshade-illumination-anchor` is set to `map`.", + "doc": "The direction of the light source(s) used to generate the hillshading with 0 as the top of the viewport if `hillshade-illumination-anchor` is set to `viewport` and due north if `hillshade-illumination-anchor` is set to `map`. Only when `hillshade-method` is set to `multidirectional` can you specify multiple light sources.", "transition": false, "sdk-support": { "basic functionality": { "js": "0.43.0", "android": "6.0.0", "ios": "4.0.0" + }, + "multidirectional": { + "js": "5.5.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3396", + "ios": "https://github.com/maplibre/maplibre-native/issues/3396" + } + }, + "expression": { + "interpolated": true, + "parameters": [ + "zoom" + ] + }, + "property-type": "data-constant" + }, + "hillshade-illumination-altitude": { + "type": "numberArray", + "default": 45, + "minimum": 0, + "maximum": 90, + "doc": "The altitude of the light source(s) used to generate the hillshading with 0 as sunset and 90 as noon. Only when `hillshade-method` is set to `multidirectional` can you specify multiple light sources.", + "transition": false, + "sdk-support": { + "basic functionality": { + "js": "5.5.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3396", + "ios": "https://github.com/maplibre/maplibre-native/issues/3396" + }, + "multidirectional": { + "js": "5.5.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3396", + "ios": "https://github.com/maplibre/maplibre-native/issues/3396" } }, "expression": { @@ -6387,15 +7584,20 @@ "property-type": "data-constant" }, "hillshade-shadow-color": { - "type": "color", + "type": "colorArray", "default": "#000000", - "doc": "The shading color of areas that face away from the light source.", + "doc": "The shading color of areas that face away from the light source(s). Only when `hillshade-method` is set to `multidirectional` can you specify multiple light sources.", "transition": true, "sdk-support": { "basic functionality": { "js": "0.43.0", "android": "6.0.0", "ios": "4.0.0" + }, + "multidirectional": { + "js": "5.5.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3396", + "ios": "https://github.com/maplibre/maplibre-native/issues/3396" } }, "expression": { @@ -6407,15 +7609,20 @@ "property-type": "data-constant" }, "hillshade-highlight-color": { - "type": "color", + "type": "colorArray", "default": "#FFFFFF", - "doc": "The shading color of areas that faces towards the light source.", + "doc": "The shading color of areas that faces towards the light source(s). Only when `hillshade-method` is set to `multidirectional` can you specify multiple light sources.", "transition": true, "sdk-support": { "basic functionality": { "js": "0.43.0", "android": "6.0.0", "ios": "4.0.0" + }, + "multidirectional": { + "js": "5.5.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3396", + "ios": "https://github.com/maplibre/maplibre-native/issues/3396" } }, "expression": { @@ -6445,6 +7652,93 @@ ] }, "property-type": "data-constant" + }, + "hillshade-method": { + "type": "enum", + "values": { + "standard": { + "doc": "The legacy hillshade method." + }, + "basic": { + "doc": "Basic hillshade. Uses a simple physics model where the reflected light intensity is proportional to the cosine of the angle between the incident light and the surface normal. Similar to GDAL's `gdaldem` default algorithm." + }, + "combined": { + "doc": "Hillshade algorithm whose intensity scales with slope. Similar to GDAL's `gdaldem` with `-combined` option." + }, + "igor": { + "doc": "Hillshade algorithm which tries to minimize effects on other map features beneath. Similar to GDAL's `gdaldem` with `-igor` option." + }, + "multidirectional": { + "doc": "Hillshade with multiple illumination directions. Uses the basic hillshade model with multiple independent light sources." + } + }, + "default": "standard", + "doc": "The hillshade algorithm to use, one of `standard`, `basic`, `combined`, `igor`, or `multidirectional`. ![image](assets/hillshade_methods.png)", + "sdk-support": { + "basic functionality": { + "js": "5.5.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3396", + "ios": "https://github.com/maplibre/maplibre-native/issues/3396" + } + }, + "expression": { + "interpolated": false, + "parameters": [ + "zoom" + ] + }, + "property-type": "data-constant" + } + }, + "paint_color-relief": { + "color-relief-opacity": { + "type": "number", + "default": 1, + "minimum": 0, + "maximum": 1, + "doc": "The opacity at which the color-relief will be drawn.", + "transition": true, + "sdk-support": { + "basic functionality": { + "js": "5.6.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3408", + "ios": "https://github.com/maplibre/maplibre-native/issues/3408" + } + }, + "expression": { + "interpolated": true, + "parameters": [ + "zoom" + ] + }, + "property-type": "data-constant" + }, + "color-relief-color": { + "type": "color", + "doc": "Defines the color of each pixel based on its elevation. Should be an expression that uses `[\"elevation\"]` as input.", + "example": [ + "interpolate", + ["linear"], + ["elevation"], + 0, "black", + 8849, "white" + ], + "transition": false, + "sdk-support": { + "basic functionality": { + "js": "5.6.0", + "android": "https://github.com/maplibre/maplibre-native/issues/3408", + "ios": "https://github.com/maplibre/maplibre-native/issues/3408" + }, + "data-driven styling": {} + }, + "expression": { + "interpolated": true, + "parameters": [ + "elevation" + ] + }, + "property-type": "color-ramp" } }, "paint_background": { diff --git a/shaders/color_relief.fragment.glsl b/shaders/color_relief.fragment.glsl new file mode 100644 index 000000000..ada54a543 --- /dev/null +++ b/shaders/color_relief.fragment.glsl @@ -0,0 +1,77 @@ +layout(std140) uniform ColorReliefDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform ColorReliefTilePropsUBO { + highp vec4 u_unpack; + highp vec2 u_dimension; + highp int u_color_ramp_size; + highp float pad_tile0; +}; + +layout(std140) uniform ColorReliefEvaluatedPropsUBO { + highp float u_opacity; + highp float pad_eval0; + highp float pad_eval1; + highp float pad_eval2; +}; + +uniform sampler2D u_image; +uniform sampler2D u_elevation_stops; +uniform sampler2D u_color_stops; + +in vec2 v_pos; + +float getElevation(vec2 coord) { + // Convert encoded elevation value to meters + vec4 data = texture(u_image, coord) * 255.0; + data.a = -1.0; + return dot(data, u_unpack); +} + +float getElevationStop(int stop) { + // Elevation stops are plain float values, not terrain-RGB encoded + float x = (float(stop) + 0.5) / float(u_color_ramp_size); + return texture(u_elevation_stops, vec2(x, 0.5)).r; +} + +vec4 getColorStop(int stop) { + float x = (float(stop) + 0.5) / float(u_color_ramp_size); + return texture(u_color_stops, vec2(x, 0.5)); +} + +void main() { + float el = getElevation(v_pos); + + // Binary search for color stops + int r = (u_color_ramp_size - 1); + int l = 0; + + while (r - l > 1) { + int m = (r + l) / 2; + float el_m = getElevationStop(m); + if (el < el_m) { + r = m; + } else { + l = m; + } + } + + // Get elevation values for interpolation + float el_l = getElevationStop(l); + float el_r = getElevationStop(r); + + // Get colors for interpolation + vec4 color_l = getColorStop(l); + vec4 color_r = getColorStop(r); + + // Interpolate between the two colors + // Guard against division by zero when el_r == el_l + float denom = el_r - el_l; + float t = (abs(denom) < 0.0001) ? 0.0 : clamp((el - el_l) / denom, 0.0, 1.0); + fragColor = u_opacity * mix(color_l, color_r, t); + +#ifdef OVERDRAW_INSPECTOR + fragColor = vec4(1.0); +#endif +} diff --git a/shaders/color_relief.vertex.glsl b/shaders/color_relief.vertex.glsl new file mode 100644 index 000000000..563e1d111 --- /dev/null +++ b/shaders/color_relief.vertex.glsl @@ -0,0 +1,26 @@ +layout(std140) uniform ColorReliefDrawableUBO { + highp mat4 u_matrix; +}; + +layout(std140) uniform ColorReliefTilePropsUBO { + highp vec4 u_unpack; + highp vec2 u_dimension; + highp int u_color_ramp_size; + highp float pad_tile0; +}; + +layout (location = 0) in vec2 a_pos; +layout (location = 1) in vec2 a_texture_pos; +out vec2 v_pos; + +void main() { + gl_Position = u_matrix * vec4(a_pos, 0, 1); + + highp vec2 epsilon = 1.0 / u_dimension; + float scale = (u_dimension.x - 2.0) / u_dimension.x; + v_pos = (a_texture_pos / 8192.0) * scale + epsilon; + + // Handle poles + if (a_pos.y < -32767.5) v_pos.y = 0.0; + if (a_pos.y > 32766.5) v_pos.y = 1.0; +} diff --git a/shaders/heatmap.vertex.glsl b/shaders/heatmap.vertex.glsl index 430b428b1..c2ac1a033 100644 --- a/shaders/heatmap.vertex.glsl +++ b/shaders/heatmap.vertex.glsl @@ -46,7 +46,7 @@ void main(void) { // weight * u_intensity * GAUSS_COEF * exp(-0.5 * 3.0^2 * S^2) == ZERO // Which solves to: // S = sqrt(-2.0 * log(ZERO / (weight * u_intensity * GAUSS_COEF))) / 3.0 - float S = sqrt(-2.0 * log(ZERO / weight / u_intensity / GAUSS_COEF)) / 3.0; + float S = sqrt(-2.0 * log(ZERO / (max(weight, ZERO) * max(u_intensity, ZERO) * GAUSS_COEF))) / 3.0; // Pass the varying in units of radius v_extrude = S * unscaled_extrude; diff --git a/shaders/hillshade.fragment.glsl b/shaders/hillshade.fragment.glsl index 499aa3c67..081a2745f 100644 --- a/shaders/hillshade.fragment.glsl +++ b/shaders/hillshade.fragment.glsl @@ -1,55 +1,185 @@ in vec2 v_pos; uniform sampler2D u_image; -layout (std140) uniform HillshadeTilePropsUBO { +layout(std140) uniform HillshadeTilePropsUBO { highp vec2 u_latrange; - highp vec2 u_light; + highp float u_exaggeration; + highp int u_method; // Hillshade method (0: STANDARD, 1: COMBINED, 2: IGOR, 3: MULTIDIRECTIONAL, 4: BASIC) + highp int u_num_lights; // Number of light sources (1-4) + highp float u_pad0; + highp float u_pad1; + highp float u_pad2; }; - -layout (std140) uniform HillshadeEvaluatedPropsUBO { - highp vec4 u_highlight; - highp vec4 u_shadow; +layout(std140) uniform HillshadeEvaluatedPropsUBO { highp vec4 u_accent; + highp vec4 u_altitudes; // Up to 4 altitude values (in radians) + highp vec4 u_azimuths; // Up to 4 azimuth values (in radians) + highp vec4 u_shadows[4]; // Shadow colors (up to 4 lights) + highp vec4 u_highlights[4]; // Highlight colors (up to 4 lights) }; #define PI 3.141592653589793 +#define STANDARD 0 +#define COMBINED 1 +#define IGOR 2 +#define MULTIDIRECTIONAL 3 +#define BASIC 4 + +float get_aspect(vec2 deriv) { + return deriv.x != 0.0 ? + atan(deriv.y, -deriv.x) : PI / 2.0 * (deriv.y > 0.0 ? 1.0 : -1.0); +} + +// MapLibre's legacy hillshade algorithm (Method 0) +void standard_hillshade(vec2 deriv) { + float azimuth = u_azimuths.x + PI; + float slope = atan(0.625 * length(deriv)); + float aspect = get_aspect(deriv); + + // Note: This implementation uses u_exaggeration as intensity, though it typically should be derived from u_altitudes.x. + float intensity = u_exaggeration; + + // Scale the slope exponentially based on intensity + float base = 1.875 - intensity * 1.75; + float maxValue = 0.5 * PI; + float scaledSlope = abs(intensity - 0.5) > 1e-6 ? ((pow(base, slope) - 1.0) / (pow(base, maxValue) - 1.0)) * maxValue : slope; + + float accent = cos(scaledSlope); + vec4 accent_color = (1.0 - accent) * u_accent * clamp(intensity * 2.0, 0.0, 1.0); + + // Calculate shadow/highlight based on aspect + float shade = abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + vec4 shade_color = mix(u_shadows[0], u_highlights[0], shade) * sin(scaledSlope) * clamp(intensity * 2.0, 0.0, 1.0); + + fragColor = accent_color * (1.0 - shade_color.a) + shade_color; +} + +// Basic directional hillshade (Method 4) +void basic_hillshade(vec2 deriv) { + deriv = deriv * u_exaggeration * 2.0; // Exaggerate the slope derivative + float azimuth = u_azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(u_altitudes.x); + float sin_alt = sin(u_altitudes.x); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); // cang is the hillshade intensity [0, 1] + + // Blend shadow and highlight based on intensity + if (shade > 0.5) { + fragColor = u_highlights[0] * (2.0 * shade - 1.0); // Highlight strength [0, 1] + } else { + fragColor = u_shadows[0] * (1.0 - 2.0 * shade); // Shadow strength [0, 1] + } +} + +// Multidirectional hillshade (Method 3) +void multidirectional_hillshade(vec2 deriv) { + deriv = deriv * u_exaggeration * 2.0; // Exaggerate the slope derivative + fragColor = vec4(0, 0, 0, 0); + + // Iterate through all light sources (up to u_num_lights, max 4) + for (int i = 0; i < 4; i++) { + if (i >= u_num_lights) break; + + // Access altitude and azimuth from vec4 UBOs + float altitude = (i == 0) ? u_altitudes.x : (i == 1) ? u_altitudes.y : (i == 2) ? u_altitudes.z : u_altitudes.w; + float azimuth = (i == 0) ? u_azimuths.x : (i == 1) ? u_azimuths.y : (i == 2) ? u_azimuths.z : u_azimuths.w; + + float cos_alt = cos(altitude); + float sin_alt = sin(altitude); + // Negate cos/sin azimuth for correct light direction in the normal calculation + float cos_az = -cos(azimuth); + float sin_az = -sin(azimuth); + + // Calculate the cosine of the angle between the light vector and the surface normal + float cang = (sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / + sqrt(1.0 + dot(deriv, deriv)); + float shade = clamp(cang, 0.0, 1.0); // cang is the hillshade intensity [0, 1] + + // Accumulate shadow/highlight contribution from each light + if (shade > 0.5) { + fragColor += u_highlights[i] * (2.0 * shade - 1.0) / float(u_num_lights); + } else { + fragColor += u_shadows[i] * (1.0 - 2.0 * shade) / float(u_num_lights); + } + } +} + +// Combined shadow and highlight method (Method 1) +void combined_hillshade(vec2 deriv) { + // Only supports one light source (index 0) + deriv = deriv * u_exaggeration * 2.0; + float azimuth = u_azimuths.x + PI; + float cos_az = cos(azimuth); + float sin_az = sin(azimuth); + float cos_alt = cos(u_altitudes.x); + float sin_alt = sin(u_altitudes.x); + + // Calculate the angle between the light vector and the surface normal + float cang = acos((sin_alt - (deriv.y * cos_az * cos_alt - deriv.x * sin_az * cos_alt)) / + sqrt(1.0 + dot(deriv, deriv))); + + cang = clamp(cang, 0.0, PI / 2.0); // Clamp angle to 90 degrees (half hemisphere) + + // The "shade" and "highlight" components are calculated from cang (angle) and the magnitude of the slope + float shade = cang * atan(length(deriv)) * 4.0 / PI / PI; + float highlight = (PI / 2.0 - cang) * atan(length(deriv)) * 4.0 / PI / PI; + + fragColor = u_shadows[0] * shade + u_highlights[0] * highlight; +} + +// Igor's shadow/highlight method (Method 2) +void igor_hillshade(vec2 deriv) { + // Only supports one light source (index 0) + deriv = deriv * u_exaggeration * 2.0; + float aspect = get_aspect(deriv); + float azimuth = u_azimuths.x + PI; + + // Slope strength is magnitude of slope vector, normalized to [0, 1] + float slope_strength = atan(length(deriv)) * 2.0 / PI; + + // Aspect strength is difference between aspect and light azimuth, normalized to [0, 1] + float aspect_strength = 1.0 - abs(mod((aspect + azimuth) / PI + 0.5, 2.0) - 1.0); + + float shadow_strength = slope_strength * aspect_strength; + float highlight_strength = slope_strength * (1.0 - aspect_strength); + + fragColor = u_shadows[0] * shadow_strength + u_highlights[0] * highlight_strength; +} + void main() { vec4 pixel = texture(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 + // Scale the derivative based on the mercator distortion at this latitude (v_pos.y) 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; + // The derivative is scaled back from [0, 1] texture range to world-space slope + // Texture range [0, 1] corresponds to slope range [-4, 4] (8.0 * 0.5 * deriv) + vec2 deriv = ((pixel.rg * 8.0) - 4.0) / scaleFactor; - // 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); - fragColor = accent_color * (1.0 - shade_color.a) + shade_color; + // Dispatch to the selected hillshade method + switch (u_method) { + case BASIC: + basic_hillshade(deriv); + break; + case COMBINED: + combined_hillshade(deriv); + break; + case IGOR: + igor_hillshade(deriv); + break; + case MULTIDIRECTIONAL: + multidirectional_hillshade(deriv); + break; + case STANDARD: + default: + standard_hillshade(deriv); + break; + } #ifdef OVERDRAW_INSPECTOR fragColor = vec4(1.0); diff --git a/shaders/hillshade_prepare.fragment.glsl b/shaders/hillshade_prepare.fragment.glsl index 9f7988750..b4ea61e4d 100644 --- a/shaders/hillshade_prepare.fragment.glsl +++ b/shaders/hillshade_prepare.fragment.glsl @@ -4,7 +4,6 @@ precision highp float; in vec2 v_pos; uniform sampler2D u_image; - layout (std140) uniform HillshadePrepareTilePropsUBO { highp vec4 u_unpack; highp vec2 u_dimension; @@ -16,13 +15,14 @@ float getElevation(vec2 coord, float bias) { // Convert encoded elevation value to meters vec4 data = texture(u_image, coord) * 255.0; data.a = -1.0; - return dot(data, u_unpack) / 4.0; + return dot(data, u_unpack); } void main() { vec2 epsilon = 1.0 / u_dimension; + float tileSize = u_dimension.x - 2.0; - // queried pixels: + // queried pixels (using Sobel operator kernel): // +-----------+ // | | | | // | a | b | c | @@ -47,30 +47,28 @@ void main() { 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 - u_maxzoom) * a) where a is an arbitrary value - // Here we use a=0.3 which works out to the expression below. see - // nickidlugash's awesome breakdown for more info + // Convert the raw pixel-space derivative (slope) into world-space slope. + // The conversion factor is: tileSize / (8 * meters_per_pixel). + // meters_per_pixel is calculated as pow(2.0, 28.2562 - u_zoom). + // The exaggeration factor is applied to scale the effect at lower zooms. + // 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; + float exaggerationFactor = u_zoom < 2.0 ? 0.4 : u_zoom < 4.5 ? 0.35 : 0.3; + float exaggeration = u_zoom < 15.0 ? (u_zoom - 15.0) * exaggerationFactor : 0.0; vec2 deriv = vec2( (c + f + f + i) - (a + d + d + g), (g + h + h + i) - (a + b + b + c) - ) / pow(2.0, (u_zoom - u_maxzoom) * exaggeration + 19.2562 - u_zoom); + ) * tileSize / pow(2.0, exaggeration + (28.2562 - u_zoom)); + // Encode the derivative into the color channels (r and g) + // The derivative is scaled from world-space slope to the range [0, 1] for texture storage. + // The maximum possible world-space derivative is assumed to be 4 (hence division by 8.0). fragColor = clamp(vec4( - deriv.x / 2.0 + 0.5, - deriv.y / 2.0 + 0.5, + deriv.x / 8.0 + 0.5, + deriv.y / 8.0 + 0.5, 1.0, 1.0), 0.0, 1.0); - #ifdef OVERDRAW_INSPECTOR fragColor = vec4(1.0); #endif diff --git a/shaders/manifest.json b/shaders/manifest.json index 8ea7f8c5b..4303fb829 100644 --- a/shaders/manifest.json +++ b/shaders/manifest.json @@ -137,6 +137,13 @@ "glsl_frag": "hillshade_prepare.fragment.glsl", "uses_ubos": true }, + { + "name": "ColorReliefShader", + "header": "color_relief", + "glsl_vert": "color_relief.vertex.glsl", + "glsl_frag": "color_relief.fragment.glsl", + "uses_ubos": true + }, { "name": "HillshadeShader", "header": "hillshade", diff --git a/src/mbgl/gfx/color_relief_drawable_data.hpp b/src/mbgl/gfx/color_relief_drawable_data.hpp new file mode 100644 index 000000000..a8f197a6a --- /dev/null +++ b/src/mbgl/gfx/color_relief_drawable_data.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace gfx { + +/** + * @brief Drawable data for color-relief layer + * + * Stores per-tile DEM properties that are set during drawable creation + * and read by the tweaker during rendering. This is necessary because + * the DEM unpack vector and stride are only available from the bucket + * during drawable creation. + */ +class ColorReliefDrawableData : public DrawableData { +public: + explicit ColorReliefDrawableData(const shaders::ColorReliefTilePropsUBO& props) + : tileProps(props) {} + + shaders::ColorReliefTilePropsUBO tileProps; +}; + +} // namespace gfx +} // namespace mbgl diff --git a/src/mbgl/gl/renderer_backend.cpp b/src/mbgl/gl/renderer_backend.cpp index cb93e9d33..3729d6b04 100644 --- a/src/mbgl/gl/renderer_backend.cpp +++ b/src/mbgl/gl/renderer_backend.cpp @@ -137,6 +137,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar shaders::BuiltIn::HeatmapTextureShader, shaders::BuiltIn::HillshadePrepareShader, shaders::BuiltIn::HillshadeShader, + shaders::BuiltIn::ColorReliefShader, shaders::BuiltIn::LineShader, shaders::BuiltIn::LineGradientShader, shaders::BuiltIn::LinePatternShader, diff --git a/src/mbgl/gl/resource_pool.cpp b/src/mbgl/gl/resource_pool.cpp index e9f8e2ad4..21903b612 100644 --- a/src/mbgl/gl/resource_pool.cpp +++ b/src/mbgl/gl/resource_pool.cpp @@ -44,6 +44,8 @@ size_t Texture2DDesc::channelCount() const { size_t Texture2DDesc::channelStorageSize() const { switch (channelType) { + case gfx::TextureChannelDataType::Float: + return 4; case gfx::TextureChannelDataType::HalfFloat: return 2; case gfx::TextureChannelDataType::UnsignedByte: diff --git a/src/mbgl/gl/texture2d.cpp b/src/mbgl/gl/texture2d.cpp index a9551dfa8..72270b79d 100644 --- a/src/mbgl/gl/texture2d.cpp +++ b/src/mbgl/gl/texture2d.cpp @@ -52,6 +52,8 @@ size_t Texture2D::getPixelStride() const noexcept { return 1 * numChannels(); case gfx::TextureChannelDataType::HalfFloat: return 2 * numChannels(); + case gfx::TextureChannelDataType::Float: + return 4 * numChannels(); default: return 0; } diff --git a/src/mbgl/layermanager/color_relief_layer_factory.cpp b/src/mbgl/layermanager/color_relief_layer_factory.cpp new file mode 100644 index 000000000..f5b516b54 --- /dev/null +++ b/src/mbgl/layermanager/color_relief_layer_factory.cpp @@ -0,0 +1,27 @@ +#include + +#include +#include +#include + +namespace mbgl { + +const style::LayerTypeInfo* ColorReliefLayerFactory::getTypeInfo() const noexcept { + return style::ColorReliefLayer::Impl::staticTypeInfo(); +} + +std::unique_ptr ColorReliefLayerFactory::createLayer( + const std::string& id, const style::conversion::Convertible& value) noexcept { + const auto source = getSource(value); + if (!source) { + return nullptr; + } + return std::unique_ptr(new (std::nothrow) style::ColorReliefLayer(id, *source)); +} + +std::unique_ptr ColorReliefLayerFactory::createRenderLayer(Immutable impl) noexcept { + assert(impl->getTypeInfo() == getTypeInfo()); + return std::make_unique(staticImmutableCast(impl)); +} + +} // namespace mbgl diff --git a/src/mbgl/mtl/renderer_backend.cpp b/src/mbgl/mtl/renderer_backend.cpp index 0511768f4..9fc8a8af1 100644 --- a/src/mbgl/mtl/renderer_backend.cpp +++ b/src/mbgl/mtl/renderer_backend.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -121,6 +122,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar shaders::BuiltIn::HeatmapTextureShader, shaders::BuiltIn::HillshadeShader, shaders::BuiltIn::HillshadePrepareShader, + shaders::BuiltIn::ColorReliefShader, shaders::BuiltIn::LineShader, shaders::BuiltIn::LineGradientShader, shaders::BuiltIn::LineSDFShader, diff --git a/src/mbgl/programs/programs.cpp b/src/mbgl/programs/programs.cpp index cedd07520..2aa093dff 100644 --- a/src/mbgl/programs/programs.cpp +++ b/src/mbgl/programs/programs.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,7 @@ void Programs::registerWith(gfx::ShaderRegistry& registry) { HeatmapTextureProgram, HillshadeProgram, HillshadePrepareProgram, + ColorReliefProgram, FillProgram, FillPatternProgram, FillOutlineProgram, diff --git a/src/mbgl/renderer/layers/color_relief_layer_tweaker.cpp b/src/mbgl/renderer/layers/color_relief_layer_tweaker.cpp new file mode 100644 index 000000000..47dee56a0 --- /dev/null +++ b/src/mbgl/renderer/layers/color_relief_layer_tweaker.cpp @@ -0,0 +1,93 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +using namespace shaders; + +void ColorReliefLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { + auto& context = parameters.context; + const auto& props = static_cast(*evaluatedProperties); + const auto& evaluated = props.evaluated; + + if (layerGroup.empty()) { + return; + } + + // Update evaluated properties UBO + ColorReliefEvaluatedPropsUBO evaluatedPropsUBO; + evaluatedPropsUBO.opacity = evaluated.get(); + evaluatedPropsUBO.pad_eval0 = 0.0f; + evaluatedPropsUBO.pad_eval1 = 0.0f; + evaluatedPropsUBO.pad_eval2 = 0.0f; + + context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &evaluatedPropsUBO); + + auto& layerUniforms = layerGroup.mutableUniformBuffers(); + layerUniforms.set(idColorReliefEvaluatedPropsUBO, evaluatedPropsUniformBuffer); + +#if MLN_UBO_CONSOLIDATION + int i = 0; + std::vector drawableUBOVector(layerGroup.getDrawableCount()); + std::vector tilePropsUBOVector(layerGroup.getDrawableCount()); +#endif + + visitLayerGroupDrawables(layerGroup, [&](gfx::Drawable& drawable) { + if (!drawable.getTileID()) { + return; + } + + const UnwrappedTileID tileID = drawable.getTileID()->toUnwrapped(); + +#if MLN_UBO_CONSOLIDATION + drawableUBOVector[i].matrix = util::cast(parameters.matrixForTile(tileID)); + + // Get tile props from drawable data (set during creation) + if (const auto& drawableData = drawable.getData()) { + if (const auto* data = static_cast(drawableData.get())) { + tilePropsUBOVector[i] = data->tileProps; + } + } + + drawable.setUBOIndex(i++); +#else + ColorReliefDrawableUBO drawableUBO; + drawableUBO.matrix = util::cast(parameters.matrixForTile(tileID)); + + auto& drawableUniforms = drawable.mutableUniformBuffers(); + drawableUniforms.createOrUpdate(idColorReliefDrawableUBO, &drawableUBO, context); + // Tile props UBO is set during drawable creation, doesn't change per frame +#endif + }); + +#if MLN_UBO_CONSOLIDATION + const size_t drawableUBOVectorSize = sizeof(ColorReliefDrawableUBO) * drawableUBOVector.size(); + if (!drawableUniformBuffer || drawableUniformBuffer->getSize() < drawableUBOVectorSize) { + drawableUniformBuffer = context.createUniformBuffer( + drawableUBOVector.data(), drawableUBOVectorSize, false, true); + } else { + drawableUniformBuffer->update(drawableUBOVector.data(), drawableUBOVectorSize); + } + + const size_t tilePropsUBOVectorSize = sizeof(ColorReliefTilePropsUBO) * tilePropsUBOVector.size(); + if (!tilePropsUniformBuffer || tilePropsUniformBuffer->getSize() < tilePropsUBOVectorSize) { + tilePropsUniformBuffer = context.createUniformBuffer( + tilePropsUBOVector.data(), tilePropsUBOVectorSize, false, true); + } else { + tilePropsUniformBuffer->update(tilePropsUBOVector.data(), tilePropsUBOVectorSize); + } + + layerUniforms.set(idColorReliefDrawableUBO, drawableUniformBuffer); + layerUniforms.set(idColorReliefTilePropsUBO, tilePropsUniformBuffer); +#endif +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/color_relief_layer_tweaker.hpp b/src/mbgl/renderer/layers/color_relief_layer_tweaker.hpp new file mode 100644 index 000000000..c17e842ec --- /dev/null +++ b/src/mbgl/renderer/layers/color_relief_layer_tweaker.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace mbgl { + +/** + Color relief layer specific tweaker + */ +class ColorReliefLayerTweaker : public LayerTweaker { +public: + ColorReliefLayerTweaker(std::string id_, Immutable properties) + : LayerTweaker(std::move(id_), properties) {} + + ~ColorReliefLayerTweaker() override = default; + + void execute(LayerGroupBase&, const PaintParameters&) override; + +protected: + gfx::UniformBufferPtr evaluatedPropsUniformBuffer; + +#if MLN_UBO_CONSOLIDATION + gfx::UniformBufferPtr drawableUniformBuffer; + gfx::UniformBufferPtr tilePropsUniformBuffer; +#endif +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp index 30448d9b3..29222cddb 100644 --- a/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp +++ b/src/mbgl/renderer/layers/hillshade_layer_tweaker.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace mbgl { @@ -15,20 +17,113 @@ using namespace style; using namespace shaders; namespace { + +// Structure to hold processed illumination properties +struct IlluminationProperties { + std::vector directionRadians; + std::vector altitudeRadians; + std::vector shadowColors; + std::vector highlightColors; + + size_t numSources() const { return directionRadians.size(); } +}; + +// Convert style properties to illumination properties +IlluminationProperties getIlluminationProperties(const HillshadePaintProperties::PossiblyEvaluated& evaluated) { + IlluminationProperties props; + + // Get the values from evaluated properties (these are now vectors) + std::vector directions = evaluated.get(); + std::vector altitudes = evaluated.get(); + std::vector highlights = evaluated.get(); + std::vector shadows = evaluated.get(); + + // Find the maximum length to ensure all arrays are the same size + size_t maxLength = std::max({directions.size(), altitudes.size(), highlights.size(), shadows.size()}); + + // Ensure we don't exceed the maximum supported + maxLength = std::min(maxLength, static_cast(MAX_ILLUMINATION_SOURCES)); + + // Pad shorter arrays by repeating the last element + auto padArray = [maxLength](auto& arr) { + if (arr.empty()) arr.push_back(typename std::decay::type::value_type{}); + while (arr.size() < maxLength) { + arr.push_back(arr.back()); + } + }; + + padArray(directions); + padArray(altitudes); + padArray(highlights); + padArray(shadows); + + // Convert degrees to radians + props.directionRadians.reserve(directions.size()); + for (float deg : directions) { + props.directionRadians.push_back(util::deg2radf(deg)); + } + + props.altitudeRadians.reserve(altitudes.size()); + for (float deg : altitudes) { + props.altitudeRadians.push_back(util::deg2radf(deg)); + } + + props.shadowColors = std::move(shadows); + props.highlightColors = std::move(highlights); + + return props; +} + +// Pack illumination properties into the UBO format +HillshadeEvaluatedPropsUBO packEvaluatedProps(const IlluminationProperties& illumination, const Color& accentColor) { + HillshadeEvaluatedPropsUBO ubo{}; + + // Pack accent color + ubo.accent = {accentColor.r, accentColor.g, accentColor.b, accentColor.a}; + + // Pack altitudes (up to 4) + for (size_t i = 0; i < illumination.altitudeRadians.size() && i < 4; ++i) { + ubo.altitudes[i] = illumination.altitudeRadians[i]; + } + + // Pack azimuths (up to 4) + for (size_t i = 0; i < illumination.directionRadians.size() && i < 4; ++i) { + ubo.azimuths[i] = illumination.directionRadians[i]; + } + + // Pack shadow colors (4 colors * 4 components = 16 floats) + for (size_t i = 0; i < illumination.shadowColors.size() && i < 4; ++i) { + const auto& color = illumination.shadowColors[i]; + ubo.shadows[i * 4 + 0] = color.r; + ubo.shadows[i * 4 + 1] = color.g; + ubo.shadows[i * 4 + 2] = color.b; + ubo.shadows[i * 4 + 3] = color.a; + } + + // Pack highlight colors (4 colors * 4 components = 16 floats) + for (size_t i = 0; i < illumination.highlightColors.size() && i < 4; ++i) { + const auto& color = illumination.highlightColors[i]; + ubo.highlights[i * 4 + 0] = color.r; + ubo.highlights[i * 4 + 1] = color.g; + ubo.highlights[i * 4 + 2] = color.b; + ubo.highlights[i * 4 + 3] = color.a; + } + + return ubo; +} + +// Convert HillshadeMethodType enum to int for shader +int32_t methodToInt(HillshadeMethodType method) { + return static_cast(method); +} + +// Calculate latitude range for tile std::array 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 {{static_cast(latlng0.latitude()), static_cast(latlng1.latitude())}}; } -std::array getLight(const PaintParameters& parameters, - const HillshadePaintProperties::PossiblyEvaluated& evaluated) { - float azimuthal = util::deg2radf(evaluated.get()); - if (evaluated.get() == HillshadeIlluminationAnchorType::Viewport) { - azimuthal = azimuthal - static_cast(parameters.state.getBearing()); - } - return {{evaluated.get(), azimuthal}}; -} } // namespace void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParameters& parameters) { @@ -43,10 +138,21 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam const auto debugGroup = parameters.encoder->createDebugGroup(label.c_str()); #endif + // Get illumination properties + auto illumination = getIlluminationProperties(evaluated); + + // Adjust azimuths if anchor is viewport + if (evaluated.get() == HillshadeIlluminationAnchorType::Viewport) { + float bearing = static_cast(parameters.state.getBearing()); + for (auto& azimuth : illumination.directionRadians) { + azimuth -= bearing; + } + } + if (!evaluatedPropsUniformBuffer || propertiesUpdated) { - const HillshadeEvaluatedPropsUBO evaluatedPropsUBO = {.highlight = evaluated.get(), - .shadow = evaluated.get(), - .accent = evaluated.get()}; + const HillshadeEvaluatedPropsUBO evaluatedPropsUBO = packEvaluatedProps(illumination, + evaluated.get()); + parameters.context.emplaceOrUpdateUniformBuffer(evaluatedPropsUniformBuffer, &evaluatedPropsUBO); propertiesUpdated = false; } @@ -83,7 +189,12 @@ void HillshadeLayerTweaker::execute(LayerGroupBase& layerGroup, const PaintParam const HillshadeTilePropsUBO tilePropsUBO = { #endif .latrange = getLatRange(tileID), - .light = getLight(parameters, evaluated) + .exaggeration = evaluated.get(), + .method = methodToInt(evaluated.get()), + .num_lights = static_cast(illumination.numSources()), + .pad0 = 0.0f, + .pad1 = 0.0f, + .pad2 = 0.0f }; #if MLN_UBO_CONSOLIDATION diff --git a/src/mbgl/renderer/layers/render_color_relief_layer.cpp b/src/mbgl/renderer/layers/render_color_relief_layer.cpp new file mode 100644 index 000000000..b40a062ac --- /dev/null +++ b/src/mbgl/renderer/layers/render_color_relief_layer.cpp @@ -0,0 +1,451 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +using namespace style; +using namespace shaders; + +namespace { + +inline const ColorReliefLayer::Impl& impl_cast(const Immutable& impl) { + assert(impl->getTypeInfo() == ColorReliefLayer::Impl::staticTypeInfo()); + return static_cast(*impl); +} + +} // namespace + +RenderColorReliefLayer::RenderColorReliefLayer(Immutable _impl) + : RenderLayer(makeMutable(std::move(_impl))), + unevaluated(impl_cast(baseImpl).paint.untransitioned()) { + styleDependencies = unevaluated.getDependencies(); + + // Initialize color ramp data with RGBA format (4 floats per stop) + colorRampSize = 256; + elevationStopsData = std::make_shared>(colorRampSize * 4); + colorStops = std::make_shared(Size{colorRampSize, 1}); + + // Initialize with default color ramp immediately to avoid uninitialized state + updateColorRamp(); +} + +RenderColorReliefLayer::~RenderColorReliefLayer() = default; + +void RenderColorReliefLayer::transition(const TransitionParameters& parameters) { + unevaluated = impl_cast(baseImpl).paint.transitioned(parameters, std::move(unevaluated)); + styleDependencies = unevaluated.getDependencies(); + updateColorRamp(); +} + +void RenderColorReliefLayer::evaluate(const PropertyEvaluationParameters& parameters) { + const auto previousProperties = staticImmutableCast(evaluatedProperties); + auto properties = makeMutable( + staticImmutableCast(baseImpl), + unevaluated.evaluate(parameters, previousProperties->evaluated)); + + passes = (properties->evaluated.get() > 0) ? RenderPass::Translucent : RenderPass::None; + properties->renderPasses = mbgl::underlying_type(passes); + evaluatedProperties = std::move(properties); + + if (layerTweaker) { + layerTweaker->updateProperties(evaluatedProperties); + } +} + +bool RenderColorReliefLayer::hasTransition() const { + return unevaluated.hasTransition(); +} + +bool RenderColorReliefLayer::hasCrossfade() const { + return false; +} + +void RenderColorReliefLayer::prepare(const LayerPrepareParameters& params) { + renderTiles = params.source->getRenderTiles(); + updateRenderTileIDs(); +} + +void RenderColorReliefLayer::updateColorRamp() { + // Ensure data structures are initialized + if (!elevationStopsData || !colorStops) { + return; + } + + // Get the color property value, using default if undefined + auto colorValue = unevaluated.get().getValue(); + if (colorValue.isUndefined()) { + colorValue = style::ColorReliefLayer::getDefaultColorReliefColor(); + if (colorValue.isUndefined()) { + // If even the default is undefined, we can't proceed + return; + } + } + + std::vector elevationStopsVector; + std::vector colorStopsVector; + + // Get the expression from ColorRampPropertyValue + // Note: getExpression() dereferences the internal pointer, so we must ensure isUndefined() is false first + const mbgl::style::expression::Expression* exprPtr = &colorValue.getExpression(); + if (!exprPtr) { + return; + } + const mbgl::style::expression::Expression& expr = *exprPtr; + + if (expr.getKind() == mbgl::style::expression::Kind::Interpolate) { + const auto* interpolate = static_cast(&expr); + + size_t stopCount = interpolate->getStopCount(); + + elevationStopsVector.reserve(stopCount); + colorStopsVector.reserve(stopCount); + + // Extract elevation values from stops + interpolate->eachStop([&](double elevation, const mbgl::style::expression::Expression& /*outputExpr*/) { + elevationStopsVector.push_back(static_cast(elevation)); + }); + + // Evaluate expression at each elevation to get colors + for (float elevation : elevationStopsVector) { + Color color = {0.0f, 0.0f, 0.0f, 0.0f}; // Default to transparent black + + // Create evaluation context with elevation as color ramp parameter + expression::EvaluationContext context(std::nullopt, nullptr, static_cast(elevation)); + + expression::EvaluationResult result = expr.evaluate(context); + + if (result && result->is()) { + color = result->get(); + } + + colorStopsVector.push_back(color); + } + + } else { + // Fallback: Sample the color ramp uniformly + const uint32_t numSamples = 256; + const float minElevation = -500.0f; + const float maxElevation = 9000.0f; + + elevationStopsVector.reserve(numSamples); + colorStopsVector.reserve(numSamples); + + for (uint32_t i = 0; i < numSamples; ++i) { + float t = static_cast(i) / static_cast(numSamples - 1); + float elevation = minElevation + t * (maxElevation - minElevation); + elevationStopsVector.push_back(elevation); + + Color color = colorValue.evaluate(static_cast(elevation)); + colorStopsVector.push_back(color); + } + } + + const uint32_t rampSize = static_cast(elevationStopsVector.size()); + if (rampSize == 0) { + return; + } + + // Resize and prepare structures + elevationStopsData->resize(rampSize * 4); // RGBA float for compatibility + colorStops->resize({rampSize, 1}); + this->colorRampSize = rampSize; + + for (uint32_t i = 0; i < rampSize; ++i) { + // Store elevation in the R channel of an RGBA float vector + (*elevationStopsData)[i * 4 + 0] = elevationStopsVector[i]; // R = elevation + (*elevationStopsData)[i * 4 + 1] = 0.0f; // G = unused + (*elevationStopsData)[i * 4 + 2] = 0.0f; // B = unused + (*elevationStopsData)[i * 4 + 3] = 1.0f; // A = unused + + // Store colors without premultiplication for proper interpolation + Color color = colorStopsVector[i]; + colorStops->data[i * 4 + 0] = static_cast(color.r * 255.0f); + colorStops->data[i * 4 + 1] = static_cast(color.g * 255.0f); + colorStops->data[i * 4 + 2] = static_cast(color.b * 255.0f); + colorStops->data[i * 4 + 3] = static_cast(color.a * 255.0f); + } + + colorRampChanged = true; +} + +static const std::string ColorReliefShaderGroupName = "ColorReliefShader"; + +void RenderColorReliefLayer::update(gfx::ShaderRegistry& shaders, + gfx::Context& context, + const TransformState&, + const std::shared_ptr&, + const RenderTree&, + UniqueChangeRequestVec& changes) { + if (!renderTiles || renderTiles->empty()) { + removeAllDrawables(); + return; + } + + // Set up layer group + if (!layerGroup) { + if (auto layerGroup_ = context.createTileLayerGroup(layerIndex, /*initialCapacity=*/64, getID())) { + setLayerGroup(std::move(layerGroup_), changes); + } else { + return; + } + } + + auto* tileLayerGroup = static_cast(layerGroup.get()); + + if (!layerTweaker) { + layerTweaker = std::make_shared(getID(), evaluatedProperties); + layerGroup->addLayerTweaker(layerTweaker); + } + + if (!colorReliefShader) { + colorReliefShader = context.getGenericShader(shaders, ColorReliefShaderGroupName); + } + + if (!colorReliefShader) { + mbgl::Log::Error(mbgl::Event::Render, "ColorRelief shader failed to load"); + removeAllDrawables(); + return; + } + + auto renderPass = RenderPass::Translucent; + if (!(mbgl::underlying_type(renderPass) & evaluatedProperties->renderPasses)) { + return; + } + + stats.drawablesRemoved += tileLayerGroup->removeDrawablesIf( + [&](gfx::Drawable& drawable) { return drawable.getTileID() && !hasRenderTile(*drawable.getTileID()); }); + + if (!staticDataSharedVertices) { + staticDataSharedVertices = std::make_shared(RenderStaticData::rasterVertices()); + } + const auto staticDataIndices = RenderStaticData::quadTriangleIndices(); + const auto staticDataSegments = RenderStaticData::rasterSegments(); + + // Update color ramp textures if changed + if (colorRampChanged && elevationStopsData && colorStops) { + if (!elevationStopsTexture) { + elevationStopsTexture = context.createTexture2D(); + } + + // Use RGBA32F instead of R32F for llvmpipe compatibility + elevationStopsTexture->setFormat(gfx::TexturePixelType::RGBA, gfx::TextureChannelDataType::Float); + elevationStopsTexture->upload(elevationStopsData->data(), Size{colorRampSize, 1}); + elevationStopsTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Nearest, + .wrapU = gfx::TextureWrapType::Clamp, + .wrapV = gfx::TextureWrapType::Clamp}); + + if (!colorStopsTexture) { + colorStopsTexture = context.createTexture2D(); + } + + colorStopsTexture->setImage(colorStops); + colorStopsTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear, + .wrapU = gfx::TextureWrapType::Clamp, + .wrapV = gfx::TextureWrapType::Clamp}); + + colorRampChanged = false; + } + + // Skip rendering if color ramp textures aren't ready + if (!elevationStopsTexture || !colorStopsTexture) { + return; + } + + std::unique_ptr builder; + + for (const RenderTile& tile : *renderTiles) { + const auto& tileID = tile.getOverscaledTileID(); + + auto* bucket_ = tile.getBucket(*baseImpl); + if (!bucket_ || !bucket_->hasData()) { + removeTile(renderPass, tileID); + continue; + } + + auto& bucket = static_cast(*bucket_); + + const auto prevBucketID = getRenderTileBucketID(tileID); + if (prevBucketID != util::SimpleIdentity::Empty && prevBucketID != bucket.getID()) { + removeTile(renderPass, tileID); + } + setRenderTileBucketID(tileID, bucket.getID()); + + // Set up tile drawable + std::shared_ptr vertices; + std::shared_ptr> indices; + auto* segments = &staticDataSegments; + + if (!bucket.vertices.empty() && !bucket.indices.empty() && !bucket.segments.empty()) { + vertices = bucket.sharedVertices; + indices = bucket.sharedIndices; + segments = &bucket.segments; + } else { + vertices = staticDataSharedVertices; + indices = std::make_shared>(staticDataIndices); + } + + if (!builder) { + builder = context.createDrawableBuilder("colorRelief"); + } + + gfx::VertexAttributeArrayPtr vertexAttrs; + auto buildVertexAttributes = [&] { + if (!vertexAttrs) { + vertexAttrs = context.createVertexAttributeArray(); + + if (const auto& attr = vertexAttrs->set(idColorReliefPosVertexAttribute)) { + attr->setSharedRawData(vertices, + offsetof(HillshadeLayoutVertex, a1), + 0, + sizeof(HillshadeLayoutVertex), + gfx::AttributeDataType::Short2); + } + + if (const auto& attr = vertexAttrs->set(idColorReliefTexturePosVertexAttribute)) { + attr->setSharedRawData(vertices, + offsetof(HillshadeLayoutVertex, a2), + 0, + sizeof(HillshadeLayoutVertex), + gfx::AttributeDataType::Short2); + } + } + return vertexAttrs; + }; + + const auto updateExisting = [&](gfx::Drawable& drawable) { + if (drawable.getLayerTweaker() != layerTweaker) { + return false; + } + + drawable.updateVertexAttributes(buildVertexAttributes(), + vertices->elements(), + gfx::Triangles(), + std::move(indices), + segments->data(), + segments->size()); + + // Update textures + auto demImagePtr = bucket.getDEMData().getImagePtr(); + if (!demImagePtr || !demImagePtr->valid()) { + return false; + } + std::shared_ptr demTexture = context.createTexture2D(); + demTexture->setImage(demImagePtr); + demTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear, + .wrapU = gfx::TextureWrapType::Clamp, + .wrapV = gfx::TextureWrapType::Clamp}); + drawable.setTexture(demTexture, idColorReliefImageTexture); + + if (elevationStopsTexture) { + drawable.setTexture(elevationStopsTexture, idColorReliefElevationStopsTexture); + } + if (colorStopsTexture) { + drawable.setTexture(colorStopsTexture, idColorReliefColorStopsTexture); + } + + return true; + }; + + if (updateTile(renderPass, tileID, std::move(updateExisting))) { + continue; + } + + builder->setShader(colorReliefShader); + builder->setDepthType(gfx::DepthMaskType::ReadOnly); + builder->setColorMode(gfx::ColorMode::alphaBlended()); + builder->setCullFaceMode(gfx::CullFaceMode::disabled()); + builder->setRenderPass(renderPass); + builder->setVertexAttributes(buildVertexAttributes()); + builder->setRawVertices({}, vertices->elements(), gfx::AttributeDataType::Short2); + builder->setSegments(gfx::Triangles(), indices->vector(), segments->data(), segments->size()); + + // Bind DEM texture + auto demImagePtr = bucket.getDEMData().getImagePtr(); + if (!demImagePtr || !demImagePtr->valid()) { + mbgl::Log::Warning(mbgl::Event::Render, "ColorRelief: DEM image not valid for tile"); + continue; // Skip this tile if DEM data is not ready + } + + const auto& demData = bucket.getDEMData(); + + std::shared_ptr demTexture = context.createTexture2D(); + demTexture->setImage(demImagePtr); + demTexture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear, + .wrapU = gfx::TextureWrapType::Clamp, + .wrapV = gfx::TextureWrapType::Clamp}); + builder->setTexture(demTexture, idColorReliefImageTexture); + + // Bind color ramp textures + if (elevationStopsTexture) { + builder->setTexture(elevationStopsTexture, idColorReliefElevationStopsTexture); + } + + if (colorStopsTexture) { + builder->setTexture(colorStopsTexture, idColorReliefColorStopsTexture); + } + + builder->flush(context); + + for (auto& drawable : builder->clearDrawables()) { + drawable->setTileID(tileID); + drawable->setLayerTweaker(layerTweaker); + + // Set up tile properties data on the drawable + shaders::ColorReliefTilePropsUBO tilePropsUBO; + + const auto unpackVector = demData.getUnpackVector(); + tilePropsUBO.unpack = {{unpackVector[0], unpackVector[1], unpackVector[2], unpackVector[3]}}; + // Use stride (dim + 2) for dimension, as the texture includes a 1-pixel border + tilePropsUBO.dimension = {{static_cast(demData.stride), static_cast(demData.stride)}}; + tilePropsUBO.color_ramp_size = static_cast(colorRampSize); + tilePropsUBO.pad_tile0 = 0.0f; + + // Store tile props in drawable data for use by the tweaker (for consolidated backends) + drawable->setData(std::make_unique(tilePropsUBO)); + +#if !MLN_UBO_CONSOLIDATION + // For non-consolidated UBOs (OpenGL), set the uniform buffer directly on the drawable + auto& drawableUniforms = drawable->mutableUniformBuffers(); + drawableUniforms.createOrUpdate(idColorReliefTilePropsUBO, &tilePropsUBO, context); +#endif + + tileLayerGroup->addDrawable(renderPass, tileID, std::move(drawable)); + ++stats.drawablesAdded; + } + } +} + +bool RenderColorReliefLayer::queryIntersectsFeature(const GeometryCoordinates&, + const GeometryTileFeature&, + float, + const TransformState&, + float, + const mat4&, + const FeatureState&) const { + return false; +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_color_relief_layer.hpp b/src/mbgl/renderer/layers/render_color_relief_layer.hpp new file mode 100644 index 000000000..473811eff --- /dev/null +++ b/src/mbgl/renderer/layers/render_color_relief_layer.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include // Added for std::vector + +namespace mbgl { +namespace gfx { +class Texture2D; +class ShaderProgramBase; +} // namespace gfx +} // namespace mbgl + +namespace mbgl { + +class RenderColorReliefLayer final : public RenderLayer { +public: + explicit RenderColorReliefLayer(Immutable); + ~RenderColorReliefLayer() override; + + /// Generate any changes needed by the layer + void update(gfx::ShaderRegistry&, + gfx::Context&, + const TransformState&, + const std::shared_ptr&, + const RenderTree&, + UniqueChangeRequestVec&) override; + +private: + void transition(const TransitionParameters&) override; + void evaluate(const PropertyEvaluationParameters&) override; + bool hasTransition() const override; + bool hasCrossfade() const override; + void prepare(const LayerPrepareParameters&) override; + bool queryIntersectsFeature(const GeometryCoordinates&, + const GeometryTileFeature&, + float, + const TransformState&, + float, + const mat4&, + const FeatureState&) const override; + void updateColorRamp(); + + // Paint properties + style::ColorReliefPaintProperties::Unevaluated unevaluated; + + // Color ramp data + uint32_t colorRampSize = 256; + bool colorRampChanged = true; + + // FIX 1: Changed type and name to correctly hold float elevation data + std::shared_ptr> elevationStopsData; // Elevation values for each stop + + std::shared_ptr colorStops; // RGB colors for each stop + + // GPU textures + std::shared_ptr elevationStopsTexture; + std::shared_ptr colorStopsTexture; + + // Shader + gfx::ShaderProgramBasePtr colorReliefShader; + + // Vertex data + using ColorReliefVertexVector = gfx::VertexVector; + std::shared_ptr staticDataSharedVertices; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/layers/render_hillshade_layer.cpp b/src/mbgl/renderer/layers/render_hillshade_layer.cpp index 872f78563..79e7a87ff 100644 --- a/src/mbgl/renderer/layers/render_hillshade_layer.cpp +++ b/src/mbgl/renderer/layers/render_hillshade_layer.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -52,9 +53,14 @@ std::array RenderHillshadeLayer::getLatRange(const UnwrappedTileID& id return {{static_cast(latlng0.latitude()), static_cast(latlng1.latitude())}}; } +// Keep old function for backward compatibility during transition std::array RenderHillshadeLayer::getLight(const PaintParameters& parameters) { const auto& evaluated = static_cast(*evaluatedProperties).evaluated; - float azimuthal = util::deg2radf(evaluated.get()); + + // Get first element from vectors (for backward compatibility) + std::vector directions = evaluated.get(); + float azimuthal = util::deg2radf(directions.empty() ? 335.0f : directions[0]); + if (evaluated.get() == HillshadeIlluminationAnchorType::Viewport) azimuthal = azimuthal - static_cast(parameters.state.getBearing()); return {{evaluated.get(), azimuthal}}; @@ -175,9 +181,11 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, if (!hillshadePrepareShader) { hillshadePrepareShader = context.getGenericShader(shaders, HillshadePrepareShaderGroupName); } + if (!hillshadeShader) { hillshadeShader = context.getGenericShader(shaders, HillshadeShaderGroupName); } + if (!hillshadePrepareShader || !hillshadeShader) { removeAllDrawables(); return; @@ -278,7 +286,8 @@ void RenderHillshadeLayer::update(gfx::ShaderRegistry& shaders, std::shared_ptr texture = context.createTexture2D(); texture->setImage(bucket.getDEMData().getImagePtr()); - texture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Linear, + // Use Nearest filtering to match GL JS behavior - the Sobel kernel samples exact pixel values + texture->setSamplerConfiguration({.filter = gfx::TextureFilterType::Nearest, .wrapU = gfx::TextureWrapType::Clamp, .wrapV = gfx::TextureWrapType::Clamp}); hillshadePrepareBuilder->setTexture(texture, idHillshadeImageTexture); diff --git a/src/mbgl/shaders/gl/shader_info.cpp b/src/mbgl/shaders/gl/shader_info.cpp index 03643e4a0..8a4df03d2 100644 --- a/src/mbgl/shaders/gl/shader_info.cpp +++ b/src/mbgl/shaders/gl/shader_info.cpp @@ -316,6 +316,24 @@ const std::vector HillshadeShaderInfo::textures = { TextureInfo{"u_image", idHillshadeImageTexture}, }; +// Color Relief +using ColorReliefShaderInfo = ShaderInfo; + +const std::vector ColorReliefShaderInfo::uniformBlocks = { + UniformBlockInfo{"ColorReliefDrawableUBO", idColorReliefDrawableUBO}, + UniformBlockInfo{"ColorReliefTilePropsUBO", idColorReliefTilePropsUBO}, + UniformBlockInfo{"ColorReliefEvaluatedPropsUBO", idColorReliefEvaluatedPropsUBO}, +}; +const std::vector ColorReliefShaderInfo::attributes = { + AttributeInfo{"a_pos", idColorReliefPosVertexAttribute}, + AttributeInfo{"a_texture_pos", idColorReliefTexturePosVertexAttribute}, +}; +const std::vector ColorReliefShaderInfo::textures = { + TextureInfo{"u_image", idColorReliefImageTexture}, + TextureInfo{"u_elevation_stops", idColorReliefElevationStopsTexture}, + TextureInfo{"u_color_stops", idColorReliefColorStopsTexture}, +}; + // Line using LineShaderInfo = ShaderInfo; diff --git a/src/mbgl/shaders/mtl/color_relief.cpp b/src/mbgl/shaders/mtl/color_relief.cpp new file mode 100644 index 000000000..14f8bf692 --- /dev/null +++ b/src/mbgl/shaders/mtl/color_relief.cpp @@ -0,0 +1,21 @@ +#include +#include +#include + +namespace mbgl { +namespace shaders { + +using ColorReliefShaderSource = ShaderSource; + +const std::array ColorReliefShaderSource::attributes = { + AttributeInfo{colorReliefUBOCount + 0, gfx::AttributeDataType::Short2, idColorReliefPosVertexAttribute}, + AttributeInfo{colorReliefUBOCount + 1, gfx::AttributeDataType::Short2, idColorReliefTexturePosVertexAttribute}, +}; +const std::array ColorReliefShaderSource::textures = { + TextureInfo{0, idColorReliefImageTexture}, + TextureInfo{1, idColorReliefElevationStopsTexture}, + TextureInfo{2, idColorReliefColorStopsTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/shader_source.cpp b/src/mbgl/shaders/shader_source.cpp index 0f819e6d6..9db6f08c7 100644 --- a/src/mbgl/shaders/shader_source.cpp +++ b/src/mbgl/shaders/shader_source.cpp @@ -6,7 +6,6 @@ namespace mbgl { using namespace shaders; -// NOLINTNEXTLINE(modernize-use-ranges) MBGL_DEFINE_ENUM(BuiltIn, {{BuiltIn::None, "None"}, {BuiltIn::Prelude, "Prelude"}, @@ -29,6 +28,7 @@ MBGL_DEFINE_ENUM(BuiltIn, {BuiltIn::HeatmapShader, "HeatmapShader"}, {BuiltIn::HeatmapTextureShader, "HeatmapTextureShader"}, {BuiltIn::HillshadePrepareShader, "HillshadePrepareShader"}, + {BuiltIn::ColorReliefShader, "ColorReliefShader"}, {BuiltIn::HillshadeShader, "HillshadeShader"}, {BuiltIn::LineShader, "LineShader"}, {BuiltIn::LineGradientShader, "LineGradientShader"}, diff --git a/src/mbgl/shaders/vulkan/color_relief.cpp b/src/mbgl/shaders/vulkan/color_relief.cpp new file mode 100644 index 000000000..0d074edb0 --- /dev/null +++ b/src/mbgl/shaders/vulkan/color_relief.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +namespace mbgl { +namespace shaders { + +using ColorReliefShaderSource = ShaderSource; + +const std::array ColorReliefShaderSource::attributes = { + AttributeInfo{0, gfx::AttributeDataType::Short2, idColorReliefPosVertexAttribute}, + AttributeInfo{1, gfx::AttributeDataType::Short2, idColorReliefTexturePosVertexAttribute}, +}; + +const std::array ColorReliefShaderSource::textures = { + TextureInfo{0, idColorReliefImageTexture}, + TextureInfo{1, idColorReliefElevationStopsTexture}, + TextureInfo{2, idColorReliefColorStopsTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/webgpu/color_relief.cpp b/src/mbgl/shaders/webgpu/color_relief.cpp new file mode 100644 index 000000000..3ce95ca28 --- /dev/null +++ b/src/mbgl/shaders/webgpu/color_relief.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +namespace mbgl { +namespace shaders { + +using ColorReliefShaderSource = ShaderSource; + +const std::array ColorReliefShaderSource::attributes = { + AttributeInfo{5, gfx::AttributeDataType::Short2, idColorReliefPosVertexAttribute}, + AttributeInfo{6, gfx::AttributeDataType::Short2, idColorReliefTexturePosVertexAttribute}, +}; + +const std::array ColorReliefShaderSource::textures = { + TextureInfo{0, idColorReliefImageTexture}, + TextureInfo{1, idColorReliefElevationStopsTexture}, + TextureInfo{2, idColorReliefColorStopsTexture}, +}; + +} // namespace shaders +} // namespace mbgl diff --git a/src/mbgl/shaders/webgpu/shader_program.cpp b/src/mbgl/shaders/webgpu/shader_program.cpp index fda5861c4..d23fb94bc 100644 --- a/src/mbgl/shaders/webgpu/shader_program.cpp +++ b/src/mbgl/shaders/webgpu/shader_program.cpp @@ -502,6 +502,10 @@ void ShaderProgram::analyzeShaderBindings(const std::string& source, WGPUShaderS if (colonPos == std::string::npos) { break; } + + // Extract the variable name + const auto varName = trimString(std::string_view(source.data() + afterVar, colonPos - afterVar)); + size_t typeStart = colonPos + 1; size_t typeEnd = typeStart; while (typeEnd < source.size()) { @@ -517,7 +521,19 @@ void ShaderProgram::analyzeShaderBindings(const std::string& source, WGPUShaderS if (typeName.find("sampler") != std::string::npos) { type = BindingType::Sampler; } else if (typeName.find("texture") != std::string::npos) { - type = BindingType::Texture; + // Check if this texture is used with textureLoad (unfilterable) or textureSample (filterable) + // Look for textureLoad(varName, which indicates it's used without filtering + std::string textureLoadPattern = std::string("textureLoad(") + std::string(varName); + std::string textureSamplePattern = std::string("textureSample(") + std::string(varName); + bool usesTextureLoad = source.find(textureLoadPattern) != std::string::npos; + bool usesTextureSample = source.find(textureSamplePattern) != std::string::npos; + + // If only textureLoad is used (not textureSample), mark as unfilterable + if (usesTextureLoad && !usesTextureSample) { + type = BindingType::UnfilterableTexture; + } else { + type = BindingType::Texture; + } } } @@ -600,6 +616,11 @@ void ShaderProgram::rebuildBindGroupLayouts() { layoutEntry.texture.viewDimension = WGPUTextureViewDimension_2D; layoutEntry.texture.multisampled = false; break; + case BindingType::UnfilterableTexture: + layoutEntry.texture.sampleType = WGPUTextureSampleType_UnfilterableFloat; + layoutEntry.texture.viewDimension = WGPUTextureViewDimension_2D; + layoutEntry.texture.multisampled = false; + break; } layoutEntries.push_back(layoutEntry); diff --git a/src/mbgl/style/conversion/color_ramp_property_value.cpp b/src/mbgl/style/conversion/color_ramp_property_value.cpp index c2d91ffb9..7621838a0 100644 --- a/src/mbgl/style/conversion/color_ramp_property_value.cpp +++ b/src/mbgl/style/conversion/color_ramp_property_value.cpp @@ -25,14 +25,17 @@ std::optional Converter::operato return std::nullopt; } assert(*expression); - if (!isFeatureConstant(**expression)) { + + auto deps = (*expression)->dependencies; + if ((deps & Dependency::Feature) && !(deps & Dependency::Elevation)) { error.message = "data expressions not supported"; return std::nullopt; } - if (!isZoomConstant(**expression)) { + if (deps & Dependency::Zoom) { error.message = "zoom expressions not supported"; return std::nullopt; } + return ColorRampPropertyValue(std::move(*expression)); } else { error.message = "color ramp must be an expression"; diff --git a/src/mbgl/style/conversion/constant.cpp b/src/mbgl/style/conversion/constant.cpp index 88f44edb8..5c895c026 100644 --- a/src/mbgl/style/conversion/constant.cpp +++ b/src/mbgl/style/conversion/constant.cpp @@ -78,6 +78,8 @@ template std::optional Converter::op Error&) const; template std::optional Converter::operator()( const Convertible&, Error&) const; +template std::optional Converter::operator()(const Convertible&, + Error&) const; template std::optional Converter::operator()(const Convertible&, Error&) const; template std::optional Converter::operator()(const Convertible&, Error&) const; template std::optional Converter::operator()(const Convertible&, Error&) const; @@ -114,6 +116,28 @@ std::optional Converter::operator()(const Convertible& value, Erro return color; } +std::optional> Converter>::operator()(const Convertible& value, + Error& error) const { + if (!isArray(value)) { + // Try single color + auto color = convert(value, error); + if (!color) return std::nullopt; + return std::vector{*color}; + } + + std::vector result; + auto length = arrayLength(value); + result.reserve(length); + + for (std::size_t i = 0; i < length; ++i) { + auto color = convert(arrayMember(value, i), error); + if (!color) return std::nullopt; + result.push_back(*color); + } + + return result; +} + std::optional Converter::operator()(const Convertible& value, Error& error) const { std::optional result; if (isArray(value)) { @@ -237,8 +261,13 @@ template std::optional> Converter>:: std::optional> Converter>::operator()(const Convertible& value, Error& error) const { + // Allow single number to be converted to a single-element array + if (std::optional number = toNumber(value)) { + return std::vector{*number}; + } + if (!isArray(value)) { - error.message = "value must be an array"; + error.message = "value must be a number or an array of numbers"; return std::nullopt; } diff --git a/src/mbgl/style/conversion/function.cpp b/src/mbgl/style/conversion/function.cpp index b5d9adcd4..3f2d21653 100644 --- a/src/mbgl/style/conversion/function.cpp +++ b/src/mbgl/style/conversion/function.cpp @@ -167,6 +167,10 @@ template std::optional> convertFunctionToE const Convertible&, Error&, bool); template std::optional> convertFunctionToExpression( const Convertible&, Error&, bool); +template std::optional>> convertFunctionToExpression>( + const Convertible&, Error&, bool); +template std::optional> convertFunctionToExpression( + const Convertible&, Error&, bool); template std::optional> convertFunctionToExpression(const Convertible&, Error&, bool); @@ -217,7 +221,19 @@ bool interpolatable(type::Type type) noexcept { [&](const type::ColorType&) { return true; }, [&](const type::PaddingType&) { return true; }, [&](const type::VariableAnchorOffsetCollectionType&) { return true; }, - [&](const type::Array& array) { return array.N && array.itemType == type::Number; }, + [&](const type::Array& array) { + // Arrays are interpolatable if they have a fixed size and item type is Number, + // OR if the item type itself is interpolatable (e.g., Array) + if (array.N && array.itemType == type::Number) { + return true; + } + // Variable-length Number arrays (like line-dasharray) are not interpolatable + // because interpolating between different dasharray patterns doesn't make semantic sense + if (!array.N && array.itemType == type::Number) { + return false; + } + return interpolatable(array.itemType); + }, [&](const auto&) { return false; }); } @@ -355,17 +371,32 @@ std::optional>> convertStops(const error.message = "function value must specify stops"; return std::nullopt; } - if (!isArray(*stopsValue)) { error.message = "function stops must be an array"; return std::nullopt; } - if (arrayLength(*stopsValue) == 0) { error.message = "function must have at least one stop"; return std::nullopt; } + // For std::vector (hillshade color arrays), parse stops as the item type + // This allows zoom function interpolation of individual colors + // Other array types (translate, dasharray, etc) use the full type + type::Type stopType = type; + bool isColorArray = false; + type.match( + [&](const type::Array& arr) { + // Check if this is an array of Color + arr.itemType.match( + [&](const type::ColorType&) { + stopType = arr.itemType; + isColorArray = true; + }, + [](const auto&) {}); + }, + [](const auto&) {}); + std::map> stops; for (std::size_t i = 0; i < arrayLength(*stopsValue); ++i) { const auto& stopValue = arrayMember(*stopsValue, i); @@ -386,14 +417,12 @@ std::optional>> convertStops(const } std::optional> e = convertLiteral( - type, arrayMember(stopValue, 1), error, convertTokens); + stopType, arrayMember(stopValue, 1), error, convertTokens); if (!e) { return std::nullopt; } - stops.emplace(*t, std::move(*e)); } - return {std::move(stops)}; } @@ -555,7 +584,28 @@ std::optional> convertIntervalFunction( } omitFirstStop(*stops); - auto expr = step(type, makeInput(true), std::move(*stops)); + // For std::vector arrays, create step with item type + type::Type exprType = type; + bool isColorArray = false; + type.match( + [&](const type::Array& arr) { + // Check if this is an array of Color + arr.itemType.match( + [&](const type::ColorType&) { + exprType = arr.itemType; + isColorArray = true; + }, + [](const auto&) {}); + }, + [](const auto&) {}); + + auto expr = step(exprType, makeInput(true), std::move(*stops)); + + // For color arrays with camera functions, return the step directly + if (isColorArray && !def) { + return expr; + } + return numberOrDefault(std::move(type), makeInput(false), std::move(expr), std::move(def)); } @@ -575,7 +625,28 @@ std::optional> convertExponentialFunction( return std::nullopt; } - auto expr = interpolate(type, exponential(*base), makeInput(true), std::move(*stops)); + // For std::vector arrays, create interpolation with item type + type::Type exprType = type; + bool isColorArray = false; + type.match( + [&](const type::Array& arr) { + // Check if this is an array of Color + arr.itemType.match( + [&](const type::ColorType&) { + exprType = arr.itemType; + isColorArray = true; + }, + [](const auto&) {}); + }, + [](const auto&) {}); + + auto expr = interpolate(exprType, exponential(*base), makeInput(true), std::move(*stops)); + + // For color arrays with camera functions, return the interpolation directly + if (isColorArray && !def) { + return expr; + } + return numberOrDefault(std::move(type), makeInput(false), std::move(expr), std::move(def)); } diff --git a/src/mbgl/style/conversion/hillshade_conversions.cpp b/src/mbgl/style/conversion/hillshade_conversions.cpp new file mode 100644 index 000000000..90cd1f5ff --- /dev/null +++ b/src/mbgl/style/conversion/hillshade_conversions.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +// Enum specializations +template <> +const char* Enum::toString(style::HillshadeMethodType t) { + switch (t) { + case style::HillshadeMethodType::Standard: + return "standard"; + case style::HillshadeMethodType::Basic: + return "basic"; + case style::HillshadeMethodType::Combined: + return "combined"; + case style::HillshadeMethodType::Igor: + return "igor"; + case style::HillshadeMethodType::Multidirectional: + return "multidirectional"; + } + return "standard"; +} + +template <> +std::optional Enum::toEnum(const std::string& s) { + if (s == "standard") return style::HillshadeMethodType::Standard; + if (s == "basic") return style::HillshadeMethodType::Basic; + if (s == "combined") return style::HillshadeMethodType::Combined; + if (s == "igor") return style::HillshadeMethodType::Igor; + if (s == "multidirectional") return style::HillshadeMethodType::Multidirectional; + return std::nullopt; +} + +namespace style { +namespace expression { + +// valueTypeToExpressionType specializations +template <> +type::Type valueTypeToExpressionType>() { + return type::Array(type::Color); +} + +template <> +type::Type valueTypeToExpressionType() { + return type::String; +} + +// ValueConverter specializations for runtime expression evaluation + +template <> +std::optional> ValueConverter>::fromExpressionValue(const Value& value) { + if (value.is>()) { + const auto& values = value.get>(); + std::vector result; + result.reserve(values.size()); + for (const auto& v : values) { + auto color = ValueConverter::fromExpressionValue(v); + if (!color) return std::nullopt; + result.push_back(*color); + } + return result; + } + + // Handle single color - wrap in array + auto color = ValueConverter::fromExpressionValue(value); + if (!color) return std::nullopt; + return std::vector{*color}; +} + +template <> +std::optional ValueConverter::fromExpressionValue(const Value& value) { + if (!value.is()) return std::nullopt; + + const auto& str = value.get(); + if (str == "standard") return HillshadeMethodType::Standard; + if (str == "basic") return HillshadeMethodType::Basic; + if (str == "combined") return HillshadeMethodType::Combined; + if (str == "igor") return HillshadeMethodType::Igor; + if (str == "multidirectional") return HillshadeMethodType::Multidirectional; + + return std::nullopt; +} + +} // namespace expression +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/conversion/property_value.cpp b/src/mbgl/style/conversion/property_value.cpp index 811a5cf1f..d86641f95 100644 --- a/src/mbgl/style/conversion/property_value.cpp +++ b/src/mbgl/style/conversion/property_value.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace mbgl { namespace style { @@ -168,6 +169,12 @@ mbgl::style::conversion::Converter>, void>:: template std::optional> Converter>::operator()( conversion::Convertible const&, conversion::Error&, bool, bool) const; +// Hillshade array and enum types +template std::optional>> Converter>>::operator()( + conversion::Convertible const&, conversion::Error&, bool, bool) const; +template std::optional> Converter>::operator()( + conversion::Convertible const&, conversion::Error&, bool, bool) const; + } // namespace conversion } // namespace style } // namespace mbgl diff --git a/src/mbgl/style/expression/compound_expression.cpp b/src/mbgl/style/expression/compound_expression.cpp index 36211667c..deff59c5f 100644 --- a/src/mbgl/style/expression/compound_expression.cpp +++ b/src/mbgl/style/expression/compound_expression.cpp @@ -369,6 +369,26 @@ const auto& heatmapDensityCompoundExpression() { return signature; } +const auto& elevationCompoundExpression() { + static auto signature = detail::makeSignature( + "elevation", + [](const EvaluationContext& params) -> Result { + // For color-relief, elevation is passed via colorRampParameter + if (params.colorRampParameter) { + return *(params.colorRampParameter); + } + // For 3D terrain, elevation is passed directly + if (params.elevation) { + return static_cast(*(params.elevation)); + } + // During parsing/validation, return a valid value to allow parsing to succeed + // The Dependency::Elevation flag prevents this from being optimized to a constant + return 0.0; + }, + Dependency::Elevation); + return signature; +} + const auto& lineProgressCompoundExpression() { static auto signature = detail::makeSignature("line-progress", [](const EvaluationContext& params) -> Result { @@ -1007,6 +1027,7 @@ constexpr const auto compoundExpressionRegistry = {"rgb", rgbCompoundExpression}, {"zoom", zoomCompoundExpression}, {"heatmap-density", heatmapDensityCompoundExpression}, + {"elevation", elevationCompoundExpression}, {"line-progress", lineProgressCompoundExpression}, {"accumulated", accumulatedCompoundExpression}, {"has", hasContextCompoundExpression}, diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp index eff15749e..380ccad8d 100644 --- a/src/mbgl/style/expression/parsing_context.cpp +++ b/src/mbgl/style/expression/parsing_context.cpp @@ -44,7 +44,8 @@ namespace style { namespace expression { namespace { -const auto requiredProps = std::array{"zoom", "heatmap-density", "line-progress", "accumulated"}; +const auto requiredProps = std::array{ + "zoom", "heatmap-density", "line-progress", "accumulated", "elevation"}; bool isConstant(const Expression& expression) { const auto kind = expression.getKind(); diff --git a/src/mbgl/style/layers/color_relief_layer.cpp b/src/mbgl/style/layers/color_relief_layer.cpp new file mode 100644 index 000000000..883237f5e --- /dev/null +++ b/src/mbgl/style/layers/color_relief_layer.cpp @@ -0,0 +1,239 @@ +// clang-format off + +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace style { + + +// static +const LayerTypeInfo* ColorReliefLayer::Impl::staticTypeInfo() noexcept { + const static LayerTypeInfo typeInfo{.type="color-relief", + .source=LayerTypeInfo::Source::Required, + .pass3d=LayerTypeInfo::Pass3D::NotRequired, + .layout=LayerTypeInfo::Layout::NotRequired, + .fadingTiles=LayerTypeInfo::FadingTiles::NotRequired, + .crossTileIndex=LayerTypeInfo::CrossTileIndex::NotRequired, + .tileKind=LayerTypeInfo::TileKind::RasterDEM}; + return &typeInfo; +} + +ColorReliefLayer::ColorReliefLayer(const std::string& layerID, const std::string& sourceID) + : Layer(makeMutable(layerID, sourceID)) { +} + +ColorReliefLayer::ColorReliefLayer(Immutable impl_) + : Layer(std::move(impl_)) { +} + +ColorReliefLayer::~ColorReliefLayer() { + weakFactory.invalidateWeakPtrs(); +} + +const ColorReliefLayer::Impl& ColorReliefLayer::impl() const { + return static_cast(*baseImpl); +} + +Mutable ColorReliefLayer::mutableImpl() const { + return makeMutable(impl()); +} + +std::unique_ptr ColorReliefLayer::cloneRef(const std::string& id_) const { + auto impl_ = mutableImpl(); + impl_->id = id_; + impl_->paint = ColorReliefPaintProperties::Transitionable(); + return std::make_unique(std::move(impl_)); +} + +void ColorReliefLayer::Impl::stringifyLayout(rapidjson::Writer&) const { +} + +// Layout properties + + +// Paint properties + +ColorRampPropertyValue ColorReliefLayer::getDefaultColorReliefColor() { + return {{}}; +} + +const ColorRampPropertyValue& ColorReliefLayer::getColorReliefColor() const { + return impl().paint.template get().value; +} + +void ColorReliefLayer::setColorReliefColor(const ColorRampPropertyValue& value) { + if (value == getColorReliefColor()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void ColorReliefLayer::setColorReliefColorTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions ColorReliefLayer::getColorReliefColorTransition() const { + return impl().paint.template get().options; +} + +PropertyValue ColorReliefLayer::getDefaultColorReliefOpacity() { + return {1.f}; +} + +const PropertyValue& ColorReliefLayer::getColorReliefOpacity() const { + return impl().paint.template get().value; +} + +void ColorReliefLayer::setColorReliefOpacity(const PropertyValue& value) { + if (value == getColorReliefOpacity()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void ColorReliefLayer::setColorReliefOpacityTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions ColorReliefLayer::getColorReliefOpacityTransition() const { + return impl().paint.template get().options; +} + +using namespace conversion; + +namespace { + +constexpr uint8_t kPaintPropertyCount = 4u; + +enum class Property : uint8_t { + ColorReliefColor, + ColorReliefOpacity, + ColorReliefColorTransition, + ColorReliefOpacityTransition, +}; + +template +constexpr uint8_t toUint8(T t) noexcept { + return uint8_t(mbgl::underlying_type(t)); +} + +constexpr const auto layerProperties = mapbox::eternal::hash_map( + {{"color-relief-color", toUint8(Property::ColorReliefColor)}, + {"color-relief-opacity", toUint8(Property::ColorReliefOpacity)}, + {"color-relief-color-transition", toUint8(Property::ColorReliefColorTransition)}, + {"color-relief-opacity-transition", toUint8(Property::ColorReliefOpacityTransition)}}); + +StyleProperty getLayerProperty(const ColorReliefLayer& layer, Property property) { + switch (property) { + case Property::ColorReliefColor: + return makeStyleProperty(layer.getColorReliefColor()); + case Property::ColorReliefOpacity: + return makeStyleProperty(layer.getColorReliefOpacity()); + case Property::ColorReliefColorTransition: + return makeStyleProperty(layer.getColorReliefColorTransition()); + case Property::ColorReliefOpacityTransition: + return makeStyleProperty(layer.getColorReliefOpacityTransition()); + } + return {}; +} + +StyleProperty getLayerProperty(const ColorReliefLayer& layer, const std::string& name) { + const auto it = layerProperties.find(name.c_str()); + if (it == layerProperties.end()) { + return {}; + } + return getLayerProperty(layer, static_cast(it->second)); +} + +} // namespace + +Value ColorReliefLayer::serialize() const { + auto result = Layer::serialize(); + assert(result.getObject()); + for (const auto& property : layerProperties) { + auto styleProperty = getLayerProperty(*this, static_cast(property.second)); + if (styleProperty.getKind() == StyleProperty::Kind::Undefined) continue; + serializeProperty(result, styleProperty, property.first.c_str(), property.second < kPaintPropertyCount); + } + return result; +} + +std::optional ColorReliefLayer::setPropertyInternal(const std::string& name, const Convertible& value) { + const auto it = layerProperties.find(name.c_str()); + if (it == layerProperties.end()) return Error{"layer doesn't support this property"}; + + auto property = static_cast(it->second); + + if (property == Property::ColorReliefColor) { + Error error; + const auto& typedValue = convert(value, error, false, false); + if (!typedValue) { + return error; + } + + setColorReliefColor(*typedValue); + return std::nullopt; + } + if (property == Property::ColorReliefOpacity) { + Error error; + const auto& typedValue = convert>(value, error, false, false); + if (!typedValue) { + return error; + } + + setColorReliefOpacity(*typedValue); + return std::nullopt; + } + + Error error; + std::optional transition = convert(value, error); + if (!transition) { + return error; + } + + if (property == Property::ColorReliefColorTransition) { + setColorReliefColorTransition(*transition); + return std::nullopt; + } + + if (property == Property::ColorReliefOpacityTransition) { + setColorReliefOpacityTransition(*transition); + return std::nullopt; + } + + return Error{"layer doesn't support this property"}; +} + +StyleProperty ColorReliefLayer::getProperty(const std::string& name) const { + return getLayerProperty(*this, name); +} + +Mutable ColorReliefLayer::mutableBaseImpl() const { + return staticMutableCast(mutableImpl()); +} + +} // namespace style +} // namespace mbgl + +// clang-format on diff --git a/src/mbgl/style/layers/color_relief_layer_impl.cpp b/src/mbgl/style/layers/color_relief_layer_impl.cpp new file mode 100644 index 000000000..0ee20a5b9 --- /dev/null +++ b/src/mbgl/style/layers/color_relief_layer_impl.cpp @@ -0,0 +1,11 @@ +#include + +namespace mbgl { +namespace style { + +bool ColorReliefLayer::Impl::hasLayoutDifference(const Layer::Impl&) const { + return false; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/color_relief_layer_impl.hpp b/src/mbgl/style/layers/color_relief_layer_impl.hpp new file mode 100644 index 000000000..7e2f4ca31 --- /dev/null +++ b/src/mbgl/style/layers/color_relief_layer_impl.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { +namespace style { + +class ColorReliefLayer::Impl : public Layer::Impl { +public: + using Layer::Impl::Impl; + + bool hasLayoutDifference(const Layer::Impl&) const override; + void stringifyLayout(rapidjson::Writer&) const override; + + ColorReliefPaintProperties::Transitionable paint; + + DECLARE_LAYER_TYPE_INFO; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/layers/color_relief_layer_properties.cpp b/src/mbgl/style/layers/color_relief_layer_properties.cpp new file mode 100644 index 000000000..63aa7561c --- /dev/null +++ b/src/mbgl/style/layers/color_relief_layer_properties.cpp @@ -0,0 +1,39 @@ +// clang-format off + +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#include + +#include + +namespace mbgl { +namespace style { + +ColorReliefLayerProperties::ColorReliefLayerProperties( + Immutable impl_) + : LayerProperties(std::move(impl_)) {} + +ColorReliefLayerProperties::ColorReliefLayerProperties( + Immutable impl_, + ColorReliefPaintProperties::PossiblyEvaluated evaluated_) + : LayerProperties(std::move(impl_)), + evaluated(std::move(evaluated_)) {} + +ColorReliefLayerProperties::~ColorReliefLayerProperties() = default; + +unsigned long ColorReliefLayerProperties::constantsMask() const { + return evaluated.constantsMask(); +} + +const ColorReliefLayer::Impl& ColorReliefLayerProperties::layerImpl() const noexcept { + return static_cast(*baseImpl); +} + +expression::Dependency ColorReliefLayerProperties::getDependencies() const noexcept { + return layerImpl().paint.getDependencies(); +} + +} // namespace style +} // namespace mbgl + +// clang-format on diff --git a/src/mbgl/style/layers/color_relief_layer_properties.hpp b/src/mbgl/style/layers/color_relief_layer_properties.hpp new file mode 100644 index 000000000..c026831fb --- /dev/null +++ b/src/mbgl/style/layers/color_relief_layer_properties.hpp @@ -0,0 +1,51 @@ +// clang-format off + +// This file is generated. Edit scripts/generate-style-code.js, then run `make style-code`. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +struct ColorReliefColor : ColorRampProperty { +}; + +struct ColorReliefOpacity : PaintProperty { + static float defaultValue() { return 1.f; } +}; + +class ColorReliefPaintProperties : public Properties< + ColorReliefColor, + ColorReliefOpacity +> {}; + +class ColorReliefLayerProperties final : public LayerProperties { +public: + explicit ColorReliefLayerProperties(Immutable); + ColorReliefLayerProperties( + Immutable, + ColorReliefPaintProperties::PossiblyEvaluated); + ~ColorReliefLayerProperties() override; + + unsigned long constantsMask() const override; + + expression::Dependency getDependencies() const noexcept override; + + const ColorReliefLayer::Impl& layerImpl() const noexcept; + // Data members. + ColorReliefPaintProperties::PossiblyEvaluated evaluated; +}; + +} // namespace style +} // namespace mbgl + +// clang-format on diff --git a/src/mbgl/style/layers/hillshade_layer.cpp b/src/mbgl/style/layers/hillshade_layer.cpp index e1af91d64..e2a614353 100644 --- a/src/mbgl/style/layers/hillshade_layer.cpp +++ b/src/mbgl/style/layers/hillshade_layer.cpp @@ -120,15 +120,15 @@ TransitionOptions HillshadeLayer::getHillshadeExaggerationTransition() const { return impl().paint.template get().options; } -PropertyValue HillshadeLayer::getDefaultHillshadeHighlightColor() { - return {Color::white()}; +PropertyValue> HillshadeLayer::getDefaultHillshadeHighlightColor() { + return {{Color::white()}}; } -const PropertyValue& HillshadeLayer::getHillshadeHighlightColor() const { +const PropertyValue>& HillshadeLayer::getHillshadeHighlightColor() const { return impl().paint.template get().value; } -void HillshadeLayer::setHillshadeHighlightColor(const PropertyValue& value) { +void HillshadeLayer::setHillshadeHighlightColor(const PropertyValue>& value) { if (value == getHillshadeHighlightColor()) return; auto impl_ = mutableImpl(); @@ -147,6 +147,33 @@ TransitionOptions HillshadeLayer::getHillshadeHighlightColorTransition() const { return impl().paint.template get().options; } +PropertyValue> HillshadeLayer::getDefaultHillshadeIlluminationAltitude() { + return {{45.f}}; +} + +const PropertyValue>& HillshadeLayer::getHillshadeIlluminationAltitude() const { + return impl().paint.template get().value; +} + +void HillshadeLayer::setHillshadeIlluminationAltitude(const PropertyValue>& value) { + if (value == getHillshadeIlluminationAltitude()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeIlluminationAltitudeTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeIlluminationAltitudeTransition() const { + return impl().paint.template get().options; +} + PropertyValue HillshadeLayer::getDefaultHillshadeIlluminationAnchor() { return {HillshadeIlluminationAnchorType::Viewport}; } @@ -174,15 +201,15 @@ TransitionOptions HillshadeLayer::getHillshadeIlluminationAnchorTransition() con return impl().paint.template get().options; } -PropertyValue HillshadeLayer::getDefaultHillshadeIlluminationDirection() { - return {335.f}; +PropertyValue> HillshadeLayer::getDefaultHillshadeIlluminationDirection() { + return {{335.f}}; } -const PropertyValue& HillshadeLayer::getHillshadeIlluminationDirection() const { +const PropertyValue>& HillshadeLayer::getHillshadeIlluminationDirection() const { return impl().paint.template get().value; } -void HillshadeLayer::setHillshadeIlluminationDirection(const PropertyValue& value) { +void HillshadeLayer::setHillshadeIlluminationDirection(const PropertyValue>& value) { if (value == getHillshadeIlluminationDirection()) return; auto impl_ = mutableImpl(); @@ -201,15 +228,42 @@ TransitionOptions HillshadeLayer::getHillshadeIlluminationDirectionTransition() return impl().paint.template get().options; } -PropertyValue HillshadeLayer::getDefaultHillshadeShadowColor() { - return {Color::black()}; +PropertyValue HillshadeLayer::getDefaultHillshadeMethod() { + return {HillshadeMethodType::Standard}; } -const PropertyValue& HillshadeLayer::getHillshadeShadowColor() const { +const PropertyValue& HillshadeLayer::getHillshadeMethod() const { + return impl().paint.template get().value; +} + +void HillshadeLayer::setHillshadeMethod(const PropertyValue& value) { + if (value == getHillshadeMethod()) + return; + auto impl_ = mutableImpl(); + impl_->paint.template get().value = value; + baseImpl = std::move(impl_); + observer->onLayerChanged(*this); +} + +void HillshadeLayer::setHillshadeMethodTransition(const TransitionOptions& options) { + auto impl_ = mutableImpl(); + impl_->paint.template get().options = options; + baseImpl = std::move(impl_); +} + +TransitionOptions HillshadeLayer::getHillshadeMethodTransition() const { + return impl().paint.template get().options; +} + +PropertyValue> HillshadeLayer::getDefaultHillshadeShadowColor() { + return {{Color::black()}}; +} + +const PropertyValue>& HillshadeLayer::getHillshadeShadowColor() const { return impl().paint.template get().value; } -void HillshadeLayer::setHillshadeShadowColor(const PropertyValue& value) { +void HillshadeLayer::setHillshadeShadowColor(const PropertyValue>& value) { if (value == getHillshadeShadowColor()) return; auto impl_ = mutableImpl(); @@ -232,20 +286,24 @@ using namespace conversion; namespace { -constexpr uint8_t kPaintPropertyCount = 12u; +constexpr uint8_t kPaintPropertyCount = 16u; enum class Property : uint8_t { HillshadeAccentColor, HillshadeExaggeration, HillshadeHighlightColor, + HillshadeIlluminationAltitude, HillshadeIlluminationAnchor, HillshadeIlluminationDirection, + HillshadeMethod, HillshadeShadowColor, HillshadeAccentColorTransition, HillshadeExaggerationTransition, HillshadeHighlightColorTransition, + HillshadeIlluminationAltitudeTransition, HillshadeIlluminationAnchorTransition, HillshadeIlluminationDirectionTransition, + HillshadeMethodTransition, HillshadeShadowColorTransition, }; @@ -258,14 +316,18 @@ constexpr const auto layerProperties = mapbox::eternal::hash_map HillshadeLayer::setPropertyInternal(const std::string& name auto property = static_cast(it->second); - if (property == Property::HillshadeAccentColor || property == Property::HillshadeHighlightColor || - property == Property::HillshadeShadowColor) { + if (property == Property::HillshadeAccentColor) { Error error; const auto& typedValue = convert>(value, error, false, false); if (!typedValue) { return error; } - if (property == Property::HillshadeAccentColor) { - setHillshadeAccentColor(*typedValue); - return std::nullopt; + setHillshadeAccentColor(*typedValue); + return std::nullopt; + } + if (property == Property::HillshadeExaggeration) { + Error error; + const auto& typedValue = convert>(value, error, false, false); + if (!typedValue) { + return error; + } + + setHillshadeExaggeration(*typedValue); + return std::nullopt; + } + if (property == Property::HillshadeHighlightColor || property == Property::HillshadeShadowColor) { + Error error; + const auto& typedValue = convert>>(value, error, false, false); + if (!typedValue) { + return error; } if (property == Property::HillshadeHighlightColor) { @@ -348,15 +432,15 @@ std::optional HillshadeLayer::setPropertyInternal(const std::string& name return std::nullopt; } } - if (property == Property::HillshadeExaggeration || property == Property::HillshadeIlluminationDirection) { + if (property == Property::HillshadeIlluminationAltitude || property == Property::HillshadeIlluminationDirection) { Error error; - const auto& typedValue = convert>(value, error, false, false); + const auto& typedValue = convert>>(value, error, false, false); if (!typedValue) { return error; } - if (property == Property::HillshadeExaggeration) { - setHillshadeExaggeration(*typedValue); + if (property == Property::HillshadeIlluminationAltitude) { + setHillshadeIlluminationAltitude(*typedValue); return std::nullopt; } @@ -375,6 +459,16 @@ std::optional HillshadeLayer::setPropertyInternal(const std::string& name setHillshadeIlluminationAnchor(*typedValue); return std::nullopt; } + if (property == Property::HillshadeMethod) { + Error error; + const auto& typedValue = convert>(value, error, false, false); + if (!typedValue) { + return error; + } + + setHillshadeMethod(*typedValue); + return std::nullopt; + } Error error; std::optional transition = convert(value, error); @@ -397,6 +491,11 @@ std::optional HillshadeLayer::setPropertyInternal(const std::string& name return std::nullopt; } + if (property == Property::HillshadeIlluminationAltitudeTransition) { + setHillshadeIlluminationAltitudeTransition(*transition); + return std::nullopt; + } + if (property == Property::HillshadeIlluminationAnchorTransition) { setHillshadeIlluminationAnchorTransition(*transition); return std::nullopt; @@ -407,6 +506,11 @@ std::optional HillshadeLayer::setPropertyInternal(const std::string& name return std::nullopt; } + if (property == Property::HillshadeMethodTransition) { + setHillshadeMethodTransition(*transition); + return std::nullopt; + } + if (property == Property::HillshadeShadowColorTransition) { setHillshadeShadowColorTransition(*transition); return std::nullopt; diff --git a/src/mbgl/style/layers/hillshade_layer_properties.hpp b/src/mbgl/style/layers/hillshade_layer_properties.hpp index a87526548..498dfbeaf 100644 --- a/src/mbgl/style/layers/hillshade_layer_properties.hpp +++ b/src/mbgl/style/layers/hillshade_layer_properties.hpp @@ -24,28 +24,38 @@ struct HillshadeExaggeration : PaintProperty { static float defaultValue() { return 0.5f; } }; -struct HillshadeHighlightColor : PaintProperty { - static Color defaultValue() { return Color::white(); } +struct HillshadeHighlightColor : PaintProperty> { + static std::vector defaultValue() { return {Color::white()}; } +}; + +struct HillshadeIlluminationAltitude : PaintProperty> { + static std::vector defaultValue() { return {45.f}; } }; struct HillshadeIlluminationAnchor : PaintProperty { static HillshadeIlluminationAnchorType defaultValue() { return HillshadeIlluminationAnchorType::Viewport; } }; -struct HillshadeIlluminationDirection : PaintProperty { - static float defaultValue() { return 335.f; } +struct HillshadeIlluminationDirection : PaintProperty> { + static std::vector defaultValue() { return {335.f}; } }; -struct HillshadeShadowColor : PaintProperty { - static Color defaultValue() { return Color::black(); } +struct HillshadeMethod : PaintProperty { + static HillshadeMethodType defaultValue() { return HillshadeMethodType::Standard; } +}; + +struct HillshadeShadowColor : PaintProperty> { + static std::vector defaultValue() { return {Color::black()}; } }; class HillshadePaintProperties : public Properties< HillshadeAccentColor, HillshadeExaggeration, HillshadeHighlightColor, + HillshadeIlluminationAltitude, HillshadeIlluminationAnchor, HillshadeIlluminationDirection, + HillshadeMethod, HillshadeShadowColor > {}; diff --git a/src/mbgl/style/layers/layer.cpp.ejs b/src/mbgl/style/layers/layer.cpp.ejs index 9121c9622..04438e8a3 100644 --- a/src/mbgl/style/layers/layer.cpp.ejs +++ b/src/mbgl/style/layers/layer.cpp.ejs @@ -77,6 +77,9 @@ layerCapabilities['hillshade'] = defaults.require('Source') .require('Pass3D') .set('TileKind', 'RasterDEM') .finalize(); +layerCapabilities['color-relief'] = defaults.require('Source') + .set('TileKind', 'RasterDEM') + .finalize(); layerCapabilities['symbol'] = defaults.require('Source') .require('Layout') .require('FadingTiles') diff --git a/src/mbgl/style/layers/location_indicator_layer.cpp b/src/mbgl/style/layers/location_indicator_layer.cpp index b443cec97..b3becaa96 100644 --- a/src/mbgl/style/layers/location_indicator_layer.cpp +++ b/src/mbgl/style/layers/location_indicator_layer.cpp @@ -194,7 +194,7 @@ TransitionOptions LocationIndicatorLayer::getAccuracyRadiusColorTransition() con } PropertyValue LocationIndicatorLayer::getDefaultBearing() { - return {0}; + return {0.f}; } const PropertyValue& LocationIndicatorLayer::getBearing() const { diff --git a/src/mbgl/style/layers/location_indicator_layer_properties.hpp b/src/mbgl/style/layers/location_indicator_layer_properties.hpp index e41979fef..fb7457659 100644 --- a/src/mbgl/style/layers/location_indicator_layer_properties.hpp +++ b/src/mbgl/style/layers/location_indicator_layer_properties.hpp @@ -44,7 +44,7 @@ struct AccuracyRadiusColor : PaintProperty { }; struct Bearing : PaintProperty { - static Rotation defaultValue() { return 0; } + static Rotation defaultValue() { return 0.f; } }; struct BearingImageSize : PaintProperty { diff --git a/src/mbgl/style/light.cpp b/src/mbgl/style/light.cpp index f3a4c7f88..742c6df59 100644 --- a/src/mbgl/style/light.cpp +++ b/src/mbgl/style/light.cpp @@ -19,7 +19,7 @@ namespace style { namespace { LightObserver nullObserver; -} +} // namespace Light::Light(Immutable impl_) : impl(std::move(impl_)), diff --git a/src/mbgl/style/light.cpp.ejs b/src/mbgl/style/light.cpp.ejs index ac3da22a2..f394002d3 100644 --- a/src/mbgl/style/light.cpp.ejs +++ b/src/mbgl/style/light.cpp.ejs @@ -20,7 +20,9 @@ namespace mbgl { namespace style { -static LightObserver nullObserver; +namespace { +LightObserver nullObserver; +} // namespace Light::Light(Immutable impl_) : impl(std::move(impl_)), observer(&nullObserver) {} diff --git a/src/mbgl/util/color.cpp b/src/mbgl/util/color.cpp index cd4258aa4..b7bf76849 100644 --- a/src/mbgl/util/color.cpp +++ b/src/mbgl/util/color.cpp @@ -1,7 +1,7 @@ #include #include - #include +#include namespace mbgl { @@ -23,6 +23,19 @@ std::string Color::stringify() const { util::toString(array[3]) + ")"; } +mbgl::Value Color::serialize() const { + // Emit as an rgba expression array for expression serialization to avoid + // "Bare objects invalid" parse errors in expression roundtrips. + const auto array = toArray(); + return std::vector{ + std::string("rgba"), + array[0], + array[1], + array[2], + array[3], + }; +} + std::array Color::toArray() const { if (a == 0) { return {{0, 0, 0, 0}}; @@ -37,21 +50,11 @@ std::array Color::toArray() const { } mbgl::Value Color::toObject() const { + // Return object format for evaluation output return mapbox::base::ValueObject{{"r", static_cast(r)}, {"g", static_cast(g)}, {"b", static_cast(b)}, {"a", static_cast(a)}}; } -mbgl::Value Color::serialize() const { - std::array array = toArray(); - return std::vector{ - std::string("rgba"), - array[0], - array[1], - array[2], - array[3], - }; -} - } // namespace mbgl diff --git a/src/mbgl/util/color.rs.cpp b/src/mbgl/util/color.rs.cpp index 4dd537015..3c83cb20c 100644 --- a/src/mbgl/util/color.rs.cpp +++ b/src/mbgl/util/color.rs.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include namespace mbgl { @@ -38,6 +40,7 @@ std::array Color::toArray() const { } mbgl::Value Color::toObject() const { + // Return object format for evaluation output return mapbox::base::ValueObject{{"r", static_cast(r)}, {"g", static_cast(g)}, {"b", static_cast(b)}, @@ -45,7 +48,9 @@ mbgl::Value Color::toObject() const { } mbgl::Value Color::serialize() const { - std::array array = toArray(); + // Emit as an rgba expression array for expression serialization to avoid + // "Bare objects invalid" parse errors in expression roundtrips. + const auto array = toArray(); return std::vector{ std::string("rgba"), array[0], diff --git a/src/mbgl/vulkan/renderer_backend.cpp b/src/mbgl/vulkan/renderer_backend.cpp index edd4d90a9..22a2b8d0c 100644 --- a/src/mbgl/vulkan/renderer_backend.cpp +++ b/src/mbgl/vulkan/renderer_backend.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -679,6 +680,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& shaders, const ProgramPar shaders::BuiltIn::ClippingMaskProgram, shaders::BuiltIn::CollisionBoxShader, shaders::BuiltIn::CollisionCircleShader, + shaders::BuiltIn::ColorReliefShader, shaders::BuiltIn::CustomGeometryShader, shaders::BuiltIn::CustomSymbolIconShader, shaders::BuiltIn::DebugShader, diff --git a/src/mbgl/webgpu/drawable.cpp b/src/mbgl/webgpu/drawable.cpp index 4c1b3f959..d93599f71 100644 --- a/src/mbgl/webgpu/drawable.cpp +++ b/src/mbgl/webgpu/drawable.cpp @@ -618,7 +618,8 @@ void Drawable::draw(PaintParameters& parameters) const { entry.sampler = texture->getSampler(); break; } - case ShaderProgram::BindingType::Texture: { + case ShaderProgram::BindingType::Texture: + case ShaderProgram::BindingType::UnfilterableTexture: { Texture2D* texture = findTextureForBinding(bindingInfo.binding, false); if (!texture) { validBindings = false; diff --git a/src/mbgl/webgpu/render_pass.cpp b/src/mbgl/webgpu/render_pass.cpp index 0930607a4..a261d437b 100644 --- a/src/mbgl/webgpu/render_pass.cpp +++ b/src/mbgl/webgpu/render_pass.cpp @@ -110,6 +110,11 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const colorAttachment.clearValue = value; } else { colorAttachment.loadOp = WGPULoadOp_Load; +#if MLN_WEBGPU_IMPL_WGPU + // wgpu-native requires clearValue to be set even when using LoadOp_Load + const WGPUColor defaultClearValue{0.0, 0.0, 0.0, 0.0}; + colorAttachment.clearValue = defaultClearValue; +#endif } WGPURenderPassDepthStencilAttachment depthAttachment = {}; diff --git a/src/mbgl/webgpu/renderer_backend.cpp b/src/mbgl/webgpu/renderer_backend.cpp index 7c6f4b985..6b526916f 100644 --- a/src/mbgl/webgpu/renderer_backend.cpp +++ b/src/mbgl/webgpu/renderer_backend.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,7 @@ void RendererBackend::initShaders(gfx::ShaderRegistry& registry, const ProgramPa shaders::BuiltIn::HeatmapTextureShader, shaders::BuiltIn::HillshadeShader, shaders::BuiltIn::HillshadePrepareShader, + shaders::BuiltIn::ColorReliefShader, shaders::BuiltIn::LineShader, shaders::BuiltIn::LineGradientShader, shaders::BuiltIn::LinePatternShader, diff --git a/test/util/color.test.cpp b/test/util/color.test.cpp index ef217ec33..c806931f9 100644 --- a/test/util/color.test.cpp +++ b/test/util/color.test.cpp @@ -26,15 +26,14 @@ const std::map> testCases = { {"rgb(-10, 0, 0)", Color(0.0f, 0.0f, 0.0f, 1.0f)}, // Clamped to 0 {"rgba(300, 0, 0, 1.0)", Color(1.0f, 0.0f, 0.0f, 1.0f)}, // Clamped to 1 {"rgba(100,100,100,0.2)", Color(20.0f / 255, 20.0f / 255, 20.0f / 255, 0.2f)}, - // {"#GGGGGG", Color(0.0f, 0.0f, 0.0f, 1.0f)}, // Treated as fallback black - // not supported right now - // {"#0F0F", Color(0.0f, 1.0f, 0.0f, 1.0f)}, - // {"#123F", Color( - // static_cast(0x1) / (0xF + 1), - // static_cast(0x2) / (0xF + 1), - // static_cast(0x3) / (0xF + 1), - // static_cast(0x4) / (0xF + 1) - // )}, + // 4-digit hex with alpha (#RGBA) + {"#0F0F", Color(0.0f, 1.0f, 0.0f, 1.0f)}, + {"#F00C", Color(0.8f, 0.0f, 0.0f, 0.8f)}, // Red with ~80% alpha + {"#123F", Color(0.067f, 0.133f, 0.2f, 1.0f)}, // Same as #123 but with full alpha + // 8-digit hex with alpha (#RRGGBBAA) + {"#FF0000FF", Color(1.0f, 0.0f, 0.0f, 1.0f)}, // Red with full alpha + {"#00FF0080", Color(0.0f, 0.5f, 0.0f, 0.5f)}, // Green with 50% alpha + {"#0000FF00", Color(0.0f, 0.0f, 0.0f, 0.0f)}, // Blue with 0% alpha (transparent) // Invalid inputs {"not-a-color", std::nullopt}, diff --git a/vendor/csscolorparser/csscolorparser.cpp b/vendor/csscolorparser/csscolorparser.cpp index 74764a22e..00b2f6617 100644 --- a/vendor/csscolorparser/csscolorparser.cpp +++ b/vendor/csscolorparser/csscolorparser.cpp @@ -191,9 +191,10 @@ std::optional parse(const std::string& css_str) { } } - // #abc and #abc123 syntax. + // #abc, #abcd, #abc123, and #abc12345 syntax. if (str.length() && str.front() == '#') { if (str.length() == 4) { + // #RGB int64_t iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. if (!(iv >= 0 && iv <= 0xfff)) { return {}; @@ -205,7 +206,21 @@ std::optional parse(const std::string& css_str) { 1 }}; } + } else if (str.length() == 5) { + // #RGBA + int64_t iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. + if (!(iv >= 0 && iv <= 0xffff)) { + return {}; + } else { + return {{ + static_cast(((iv & 0xf000) >> 8) | ((iv & 0xf000) >> 12)), + static_cast(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)), + static_cast((iv & 0xf0) | ((iv & 0xf0) >> 4)), + static_cast(((iv & 0xf) | ((iv & 0xf) << 4))) / 255.0f + }}; + } } else if (str.length() == 7) { + // #RRGGBB int64_t iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. if (!(iv >= 0 && iv <= 0xffffff)) { return {}; // Covers NaN. @@ -217,6 +232,19 @@ std::optional parse(const std::string& css_str) { 1 }}; } + } else if (str.length() == 9) { + // #RRGGBBAA + int64_t iv = parseInt(str.substr(1), 16); // TODO(deanm): Stricter parsing. + if (!(iv >= 0 && iv <= 0xffffffff)) { + return {}; // Covers NaN. + } else { + return {{ + static_cast((iv & 0xff000000) >> 24), + static_cast((iv & 0xff0000) >> 16), + static_cast((iv & 0xff00) >> 8), + static_cast(iv & 0xff) / 255.0f + }}; + } } return {};