diff --git a/.github/scripts/free_disk_space.sh b/.github/scripts/free_disk_space.sh new file mode 100755 index 000000000000..7982cd2560d3 --- /dev/null +++ b/.github/scripts/free_disk_space.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# +# The Azure provided machines typically have the following disk allocation: +# Total space: 85GB +# Allocated: 67 GB +# Free: 17 GB +# This script frees up 28 GB of disk space by deleting unneeded packages and +# large directories. +# The Flink end to end tests download and generate more than 17 GB of files, +# causing unpredictable behavior and build failures. +# +echo "==============================================================================" +echo "Freeing up disk space on CI system" +echo "==============================================================================" + +echo "Listing 100 largest packages" +dpkg-query -Wf '${Installed-Size}\t${Package}\n' | sort -n | tail -n 100 +df -h +echo "Removing large packages" +sudo apt-get remove -y '^ghc-8.*' +sudo apt-get remove -y '^dotnet-.*' +sudo apt-get remove -y '^llvm-.*' +sudo apt-get remove -y 'php.*' +sudo apt-get remove -y azure-cli google-cloud-sdk hhvm google-chrome-stable firefox powershell mono-devel +sudo apt-get autoremove -y +sudo apt-get clean +df -h +echo "Removing large directories" +# deleting 15GB +rm -rf /usr/share/dotnet/ +df -h diff --git a/.github/workflows/android-ci.yml b/.github/workflows/android-ci.yml index c9b33c7c71c6..31e95ad36f6c 100644 --- a/.github/workflows/android-ci.yml +++ b/.github/workflows/android-ci.yml @@ -2,6 +2,8 @@ name: android-ci permissions: id-token: write # needed for AWS + contents: write + actions: write on: push: @@ -15,6 +17,7 @@ on: branches: - "*" + concurrency: # cancel jobs on PRs only group: ${{ github.workflow }}-${{ github.ref }} @@ -54,8 +57,8 @@ jobs: BUILDTYPE: Debug IS_LOCAL_DEVELOPMENT: false MLN_ANDROID_STL: c++_static - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN_ANDROID }} + # SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + # SENTRY_DSN: ${{ secrets.SENTRY_DSN_ANDROID }} steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4 with: @@ -96,10 +99,10 @@ jobs: rm -rf venv shell: bash - - if: env.SENTRY_DSN != '' - run: | - echo "SENTRY_ORG=maplibre" >> "$GITHUB_ENV" - echo "SENTRY_PROJECT=maplibre-android" >> "$GITHUB_ENV" + # - if: env.SENTRY_DSN != '' + # run: | + # echo "SENTRY_ORG=maplibre" >> "$GITHUB_ENV" + # echo "SENTRY_PROJECT=maplibre-android" >> "$GITHUB_ENV" - uses: infotroph/tree-is-clean@69d598a958e8cb8f3d0e3d52b5ebcd8684f2adc2 # v1.0.6 with: @@ -191,13 +194,13 @@ jobs: cp MapLibreAndroidTestApp/build/outputs/apk/${{ matrix.renderer }}/debug/MapLibreAndroidTestApp-${{ matrix.renderer }}-debug.apk InstrumentationTestApp${{ env.renderer }}.apk cp MapLibreAndroidTestApp/build/outputs/apk/androidTest/${{ matrix.renderer }}/debug/MapLibreAndroidTestApp-${{ matrix.renderer }}-debug-androidTest.apk InstrumentationTests${{ env.renderer }}.apk - - name: Install sentry-cli - if: env.SENTRY_DSN != '' - run: curl -sL https://sentry.io/get-cli/ | sh + # - name: Install sentry-cli + # if: env.SENTRY_DSN != '' + # run: curl -sL https://sentry.io/get-cli/ | sh - - name: Upload debug symbols to sentry - if: env.SENTRY_DSN != '' - run: sentry-cli debug-files upload MapLibreAndroidTestApp + # - name: Upload debug symbols to sentry + # if: env.SENTRY_DSN != '' + # run: sentry-cli debug-files upload MapLibreAndroidTestApp - name: Upload android-ui-test-${{ matrix.renderer }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 diff --git a/.github/workflows/android-device-test.yml b/.github/workflows/android-device-test.yml index c94963004226..a4d0674ebf9b 100644 --- a/.github/workflows/android-device-test.yml +++ b/.github/workflows/android-device-test.yml @@ -1,10 +1,7 @@ name: android-device-test on: - workflow_run: - workflows: [android-ci] - types: - - completed + workflow_dispatch: permissions: id-token: write # needed for AWS diff --git a/.github/workflows/android-release.yml b/.github/workflows/android-release.yml index 09cbfa9cd289..0333f9cd1ab9 100644 --- a/.github/workflows/android-release.yml +++ b/.github/workflows/android-release.yml @@ -62,7 +62,7 @@ jobs: android-build-and-upload-release: needs: android-create-release - runs-on: MapLibre_Native_Ubuntu_24_04_x84_16_core + runs-on: ubuntu-24.04 defaults: run: working-directory: platform/android @@ -82,6 +82,28 @@ jobs: with: submodules: recursive + - name: Free disk space + working-directory: . + run: .github/scripts/free_disk_space.sh + + - name: Setup build directories on /mnt + working-directory: . + run: | + echo "Disk space before setup:" + df -h + + # Create build directories on /mnt + sudo mkdir -p /mnt/gradle /mnt/build /mnt/android-sdk + sudo chown -R $USER:$USER /mnt/gradle /mnt/build /mnt/android-sdk + + # Create symlinks for build directories + mkdir -p platform/android + ln -sfn /mnt/build platform/android/build || true + + # Configure gradle to use /mnt + echo "gradle.user.home=/mnt/gradle" >> gradle.properties || true + echo "buildDir=/mnt/build" >> gradle.properties || true + - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v4 with: node-version-file: ".nvmrc" @@ -127,28 +149,16 @@ jobs: platform/android/MapLibreAndroid/build/outputs/aar/MapLibreAndroid-${{ matrix.RENDERER }}-${{ env.buildtype }}-${{ needs.android-create-release.outputs.version_tag }}.aar platform/android/build/debug-symbols-maplibre-android-${{ matrix.RENDERER }}-${{ env.buildtype }}-${{ needs.android-create-release.outputs.version_tag }}.tar.gz - - name: Prepare MavenCentral release + - name: Publish to Github env: - GPG_KEY_CONTENTS: ${{ secrets.GPG_KEY_CONTENTS }} + GITHUB_TOKEN: ${{ secrets.MAPLIBRE_NATIVE_PRIVATE_TOKEN }} run: | - echo "${GPG_KEY_CONTENTS}" | base64 -d > signing-key.gpg - shell: bash - - - name: Publish to MavenCentral - run: | - renderer=${{ matrix.RENDERER }} - if [ "$renderer" = "opengl" ]; then - ./gradlew :MapLibreAndroid:publishOpengl${{ env.buildtype }}PublicationToSonatypeRepository - ./gradlew :MapLibreAndroid:publishDefault${{ env.buildtype }}PublicationToSonatypeRepository - else - ./gradlew :MapLibreAndroid:publishVulkan${{ env.buildtype }}PublicationToSonatypeRepository - fi - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} + renderer=${{ matrix.RENDERER }} + if [ "$renderer" = "opengl" ]; then + ./gradlew :MapLibreAndroid:publishDefault${{ env.buildtype }}PublicationToGithubPackagesRepository + else + ./gradlew :MapLibreAndroid:publishVulkan${{ env.buildtype }}PublicationToGithubPackagesRepository + fi finilize-release: runs-on: ubuntu-latest @@ -163,27 +173,12 @@ jobs: name: ${{ needs.android-create-release.outputs.version_tag }} draft: false - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4 - - - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v4 - with: - distribution: "temurin" - java-version: "17" - - - name: Close Sonatype staging repository - run: ./gradlew closeSonatypeStagingRepository - working-directory: platform/android - env: - MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} - MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} - - run: npm install working-directory: . - name: Write release notifications if: github.repository_owner == 'maplibre' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.MAPLIBRE_NATIVE_PRIVATE_TOKEN }} run: node .github/scripts/notify-release-on-prs.ts --tag ${{ needs.android-create-release.outputs.version_tag }} working-directory: . diff --git a/.github/workflows/ios-ci.yml b/.github/workflows/ios-ci.yml index 8ea0abcaacc8..7fe7e873c2c5 100644 --- a/.github/workflows/ios-ci.yml +++ b/.github/workflows/ios-ci.yml @@ -59,7 +59,7 @@ jobs: ios-build: needs: pre_job if: needs.pre_job.outputs.should_skip != 'true' - runs-on: [self-hosted, macOS, ARM64] + runs-on: [macos-14] concurrency: # cancel jobs on PRs only group: ${{ github.workflow }}-${{ github.ref }} @@ -86,8 +86,8 @@ jobs: ${{ runner.os }}-bazel- path: ~/.cache/bazel - - name: Build Example (Swift) App - run: bazel build //platform/ios/app-swift:MapLibreApp --//:renderer=metal + # - name: Build Example (Swift) App + # run: bazel build //platform/ios/app-swift:MapLibreApp --//:renderer=metal - name: Check debug symbols run: bazel run //platform:check-public-symbols --//:renderer=metal @@ -126,92 +126,91 @@ jobs: # render test - - name: Build RenderTest .ipa and .xctest for AWS Device Farm - run: | - set -e - bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" - build_dir="$(mktemp -d)" - xcodebuild build-for-testing -scheme RenderTest -project MapLibre.xcodeproj -derivedDataPath "$build_dir" - render_test_app_dir="$(dirname "$(find "$build_dir" -name RenderTestApp.app)")" - cd "$render_test_app_dir" - mkdir Payload - mv RenderTestApp.app Payload - zip -r RenderTestApp.zip Payload - mv RenderTestApp.zip RenderTestApp.ipa - cd Payload/RenderTestApp.app/PlugIns - zip -r "$render_test_app_dir"/RenderTest.xctest.zip RenderTest.xctest - echo render_test_artifacts_dir="$render_test_app_dir" >> "$GITHUB_ENV" - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ios-render-test - retention-days: 3 - if-no-files-found: error - path: | - ${{ env.render_test_artifacts_dir }}/RenderTest.xctest.zip - ${{ env.render_test_artifacts_dir }}/RenderTestApp.ipa + # - name: Build RenderTest .ipa and .xctest for AWS Device Farm + # run: | + # set -e + # bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" + # build_dir="$(mktemp -d)" + # xcodebuild build-for-testing -scheme RenderTest -project MapLibre.xcodeproj -derivedDataPath "$build_dir" + # render_test_app_dir="$(dirname "$(find "$build_dir" -name RenderTestApp.app)")" + # cd "$render_test_app_dir" + # mkdir Payload + # mv RenderTestApp.app Payload + # zip -r RenderTestApp.zip Payload + # mv RenderTestApp.zip RenderTestApp.ipa + # cd Payload/RenderTestApp.app/PlugIns + # zip -r "$render_test_app_dir"/RenderTest.xctest.zip RenderTest.xctest + # echo render_test_artifacts_dir="$render_test_app_dir" >> "$GITHUB_ENV" + + # - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # with: + # name: ios-render-test + # retention-days: 3 + # if-no-files-found: error + # path: | + # ${{ env.render_test_artifacts_dir }}/RenderTest.xctest.zip + # ${{ env.render_test_artifacts_dir }}/RenderTestApp.ipa # C++ unit tests - - name: Build CppUnitTests .ipa and .xctest for AWS Device Farm - run: | - set -e - bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" - build_dir="$(mktemp -d)" - xcodebuild build-for-testing -scheme CppUnitTests -project MapLibre.xcodeproj -derivedDataPath "$build_dir" - ios_cpp_test_app_dir="$(dirname "$(find "$build_dir" -name CppUnitTestsApp.app)")" - cd "$ios_cpp_test_app_dir" - mkdir Payload - mv CppUnitTestsApp.app Payload - zip -r CppUnitTestsApp.zip Payload - mv CppUnitTestsApp.zip CppUnitTestsApp.ipa - cd Payload/CppUnitTestsApp.app/PlugIns - zip -r "$ios_cpp_test_app_dir"/CppUnitTests.xctest.zip CppUnitTests.xctest - echo ios_cpp_test_artifacts_dir="$ios_cpp_test_app_dir" >> "$GITHUB_ENV" - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ios-cpp-unit-tests - retention-days: 3 - if-no-files-found: error - path: | - ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTests.xctest.zip - ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTestsApp.ipa + # - name: Build CppUnitTests .ipa and .xctest for AWS Device Farm + # run: | + # set -e + # bazel run //platform/ios:xcodeproj --@rules_xcodeproj//xcodeproj:extra_common_flags="--//:renderer=metal" + # build_dir="$(mktemp -d)" + # xcodebuild build-for-testing -scheme CppUnitTests -project MapLibre.xcodeproj -derivedDataPath "$build_dir" + # ios_cpp_test_app_dir="$(dirname "$(find "$build_dir" -name CppUnitTestsApp.app)")" + # cd "$ios_cpp_test_app_dir" + # mkdir Payload + # mv CppUnitTestsApp.app Payload + # zip -r CppUnitTestsApp.zip Payload + # mv CppUnitTestsApp.zip CppUnitTestsApp.ipa + # cd Payload/CppUnitTestsApp.app/PlugIns + # zip -r "$ios_cpp_test_app_dir"/CppUnitTests.xctest.zip CppUnitTests.xctest + # echo ios_cpp_test_artifacts_dir="$ios_cpp_test_app_dir" >> "$GITHUB_ENV" + + # - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + # with: + # name: ios-cpp-unit-tests + # retention-days: 3 + # if-no-files-found: error + # path: | + # ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTests.xctest.zip + # ${{ env.ios_cpp_test_artifacts_dir }}/CppUnitTestsApp.ipa # Size test (Bloaty) - - name: Build dynamic library for size test (Bloaty) - run: | - bazel build //platform/ios:MapLibre.dynamic --//:renderer=metal --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym - bazel_bin="$(bazel info --compilation_mode="opt" bazel-bin)" - unzip "$bazel_bin"/platform/ios/MapLibre.dynamic.xcframework.zip - cp "$bazel_bin"/platform/ios/MapLibre.dynamic_dsyms/MapLibre_ios_device.framework.dSYM/Contents/Resources/DWARF/MapLibre_ios_device MapLibre_DWARF - cp MapLibre.xcframework/ios-arm64/MapLibre.framework/MapLibre MapLibre_dynamic - - - name: Upload size test as artifact (Bloaty) - if: github.event_name == 'pull_request' - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ios-size-test-files - retention-days: 3 - if-no-files-found: error - path: | - platform/ios/MapLibre_DWARF - platform/ios/MapLibre_dynamic - - - name: Configure AWS Credentials - if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME - uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v4 - with: - aws-region: us-west-2 - role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} - role-session-name: ${{ github.run_id }} - - - name: Upload MapLibre_DWARF & MapLibre_dynamic to S3 - if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME - run: | - aws s3 cp MapLibre_DWARF s3://maplibre-native/size-test-ios/MapLibre_DWARF-main - aws s3 cp MapLibre_dynamic s3://maplibre-native/size-test-ios/MapLibre_dynamic-main + # - name: Build dynamic library for size test (Bloaty) + # run: | + # bazel build //platform/ios:MapLibre.dynamic --//:renderer=metal --compilation_mode="opt" --copt -g --copt="-Oz" --strip never --output_groups=+dsyms --apple_generate_dsym + # bazel_bin="$(bazel info --compilation_mode="opt" bazel-bin)" + # unzip "$bazel_bin"/platform/ios/MapLibre.dynamic.xcframework.zip + # cp "$bazel_bin"/platform/ios/MapLibre.dynamic_dsyms/MapLibre_ios_device.framework.dSYM/Contents/Resources/DWARF/MapLibre_ios_device MapLibre_DWARF + # cp MapLibre.xcframework/ios-arm64/MapLibre.framework/MapLibre MapLibre_dynamic + + # - name: Upload size test as artifact (Bloaty) + # if: github.event_name == 'pull_request' + # with: + # name: ios-size-test-files + # retention-days: 3 + # if-no-files-found: error + # path: | + # platform/ios/MapLibre_DWARF + # platform/ios/MapLibre_dynamic + + # - name: Configure AWS Credentials + # if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + # uses: aws-actions/configure-aws-credentials@v4 + # with: + # aws-region: us-west-2 + # role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} + # role-session-name: ${{ github.run_id }} + + # - name: Upload MapLibre_DWARF & MapLibre_dynamic to S3 + # if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME + # run: | + # aws s3 cp MapLibre_DWARF s3://maplibre-native/size-test-ios/MapLibre_DWARF-main + # aws s3 cp MapLibre_dynamic s3://maplibre-native/size-test-ios/MapLibre_dynamic-main - if: github.event_name == 'pull_request' uses: ./.github/actions/save-pr-number @@ -292,16 +291,16 @@ jobs: awk '/^##/ { p = 0 }; p == 1 { print }; $0 == "${{ env.changelog_version_heading }}" { p = 1 };' CHANGELOG.md > changelog_for_version.md cat changelog_for_version.md - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 # v4 - with: - aws-region: us-west-2 - role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} - role-session-name: ${{ github.run_id }} + # - name: Configure AWS Credentials + # uses: aws-actions/configure-aws-credentials@v4 + # with: + # aws-region: us-west-2 + # role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }} + # role-session-name: ${{ github.run_id }} - - name: Upload changelog to S3 - if: env.make_release - run: aws s3 cp changelog_for_version.md s3://maplibre-native/changelogs/ios-${{ env.version }}.md + # - name: Upload changelog to S3 + # if: env.make_release + # run: aws s3 cp changelog_for_version.md s3://maplibre-native/changelogs/ios-${{ env.version }}.md - name: Create tag if: env.make_release @@ -317,7 +316,7 @@ jobs: zip MapLibre.dynamic.xcframework.zip LICENSE.md # add license to zip working-directory: . - - name: Release (GitHub) - Create Draft + - name: Release (GitHub) if: env.make_release id: github_release uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 @@ -330,57 +329,56 @@ jobs: prerelease: ${{ github.event.inputs.release == 'pre' }} body_path: platform/ios/changelog_for_version.md fail_on_unmatched_files: true - draft: true + # draft: true # needed to trigger workflow for Swift Package Index release - - name: Generate token - if: env.make_release - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 - id: generate_token - with: - app-id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} - private-key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} + # - name: Generate token + # if: env.make_release + # id: generate_token + # uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 + # with: + # app_id: ${{ secrets.MAPLIBRE_NATIVE_BOT_APP_ID }} + # private_key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }} - name: Release (Swift Package Index) if: env.make_release run: | - echo "::add-mask::${{ steps.generate_token.outputs.token }}" - release_workflow_id=81221759 # id of release.yml + echo "::add-mask::${{ secrets.MAPLIBRE_NATIVE_PRIVATE_TOKEN }}" + release_workflow_name=release.yml # github api allows you to pass workflow name instead of id curl -L \ -X POST \ -H "Accept: application/vnd.github+json" \ - -H "Authorization: token ${{ steps.generate_token.outputs.token }}" \ + -H "Authorization: token ${{ secrets.MAPLIBRE_NATIVE_PRIVATE_TOKEN }}" \ -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/maplibre/maplibre-gl-native-distribution/actions/workflows/$release_workflow_id/dispatches \ + https://api.github.com/repos/hudhud-maps/maplibre-gl-native-distribution/actions/workflows/$release_workflow_name/dispatches \ -d '{"ref":"main","inputs":{ - "changelog_url": "https://maplibre-native.s3.eu-central-1.amazonaws.com/changelogs/ios-${{ env.version }}.md", "version":"${{ env.version }}", "download_url":"${{ fromJSON(steps.github_release.outputs.assets)[0].browser_download_url }}"}}' - - run: npm install - working-directory: . - - - name: Release (CocoaPods) - shell: bash -leo pipefail {0} # so pod is found - if: env.make_release - run: gh workflow run ios-release-cocoapods.yml --field version=${{ env.version }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Publish release (remove draft) - if: env.make_release - uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 - with: - tag_name: ios-v${{ env.version }} - name: ios-v${{ env.version }} - draft: false - - - name: Write release notifications - if: env.make_release && github.repository_owner == 'maplibre' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: node .github/scripts/notify-release-on-prs.ts --tag ios-v${{ env.version }} - working-directory: . + # - run: npm install + # working-directory: . + + # - name: Release (CocoaPods) + # shell: bash -leo pipefail {0} # so pod is found + # if: env.make_release + # run: gh workflow run ios-release-cocoapods.yml --field version=${{ env.version }} + # env: + # GH_TOKEN: ${{ secrets.MAPLIBRE_NATIVE_PRIVATE_TOKEN }} + + # - name: Publish release (remove draft) + # if: env.make_release + # uses: softprops/action-gh-release@6cbd405e2c4e67a21c47fa9e383d020e4e28b836 # v2.3.3 + # with: + # tag_name: ios-v${{ env.version }} + # name: ios-v${{ env.version }} + # draft: false + + # - name: Write release notifications + # if: env.make_release && github.repository_owner == 'maplibre' + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # run: node .github/scripts/notify-release-on-prs.ts --tag ios-v${{ env.version }} + # working-directory: . ios-build-cmake: needs: pre_job diff --git a/CMakeLists.txt b/CMakeLists.txt index b768f34424a6..82a7ef46dd91 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -531,6 +531,8 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/map/map_projection.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform_active.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform_active.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform_state.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/transform_state.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/map/zoom_history.hpp @@ -963,11 +965,18 @@ list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_properties.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_impl.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_factory.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_style_filter.hpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_render.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_properties.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_impl.cpp ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_layer_factory.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_file_source.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/plugin_style_filter.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection.cpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection_bucket.hpp + ${PROJECT_SOURCE_DIR}/src/mbgl/plugin/feature_collection_bucket.cpp ) diff --git a/bazel/core.bzl b/bazel/core.bzl index 6271e28ade28..f8678e3059ab 100644 --- a/bazel/core.bzl +++ b/bazel/core.bzl @@ -4,6 +4,10 @@ MLN_LAYER_PLUGIN_HEADERS = [ "src/mbgl/plugin/plugin_layer_impl.hpp", "src/mbgl/plugin/plugin_layer_render.hpp", "src/mbgl/plugin/plugin_layer_properties.hpp", + "src/mbgl/plugin/plugin_file_source.hpp", + "src/mbgl/plugin/plugin_style_filter.hpp", + "src/mbgl/plugin/feature_collection_bucket.hpp", + "src/mbgl/plugin/feature_collection.hpp", ] MLN_LAYER_PLUGIN_SOURCE = [ @@ -12,6 +16,9 @@ MLN_LAYER_PLUGIN_SOURCE = [ "src/mbgl/plugin/plugin_layer_impl.cpp", "src/mbgl/plugin/plugin_layer_render.cpp", "src/mbgl/plugin/plugin_layer_properties.cpp", + "src/mbgl/plugin/plugin_style_filter.cpp", + "src/mbgl/plugin/feature_collection_bucket.cpp", + "src/mbgl/plugin/feature_collection.cpp", ] MLN_PUBLIC_GENERATED_STYLE_HEADERS = [ @@ -141,6 +148,7 @@ MLN_CORE_SOURCE = [ "src/mbgl/gfx/attribute.cpp", "src/mbgl/gfx/attribute.hpp", "src/mbgl/gfx/cull_face_mode.hpp", + "src/mbgl/gfx/scissor_rect.hpp", "src/mbgl/gfx/fill_generator.cpp", "src/mbgl/gfx/index_buffer.hpp", "src/mbgl/gfx/index_vector.hpp", @@ -188,6 +196,8 @@ MLN_CORE_SOURCE = [ "src/mbgl/map/map_projection.cpp", "src/mbgl/map/transform.cpp", "src/mbgl/map/transform.hpp", + "src/mbgl/map/transform_active.cpp", + "src/mbgl/map/transform_active.hpp", "src/mbgl/map/transform_state.cpp", "src/mbgl/map/transform_state.hpp", "src/mbgl/map/zoom_history.hpp", diff --git a/benchmark/android/.gitignore b/benchmark/android/.gitignore index 3bb19ffd7868..efe951c706c4 100644 --- a/benchmark/android/.gitignore +++ b/benchmark/android/.gitignore @@ -1 +1,5 @@ .gradle/ +.project +.settings +.classpath +build/ diff --git a/include/mbgl/gl/renderer_backend.hpp b/include/mbgl/gl/renderer_backend.hpp index b95988589fbe..494bdae9e6b5 100644 --- a/include/mbgl/gl/renderer_backend.hpp +++ b/include/mbgl/gl/renderer_backend.hpp @@ -45,7 +45,7 @@ class RendererBackend : public gfx::RendererBackend { /// It sets the internal assumed state to the supplied values. void assumeFramebufferBinding(FramebufferID fbo); void assumeViewport(int32_t x, int32_t y, const Size&); - void assumeScissorTest(bool); + void assumeScissorTest(int32_t, int32_t, uint32_t, uint32_t); /// Returns true when assumed framebuffer binding hasn't changed from the implicit binding. bool implicitFramebufferBound(); @@ -55,7 +55,7 @@ class RendererBackend : public gfx::RendererBackend { /// match the supplied values. void setFramebufferBinding(FramebufferID fbo); void setViewport(int32_t x, int32_t y, const Size&); - void setScissorTest(bool); + void setScissorTest(int32_t, int32_t, uint32_t, uint32_t); }; } // namespace gl diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index 3bdacf5e8bed..d50cfc501d3e 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -74,6 +74,8 @@ constexpr bool operator!=(const CameraOptions& a, const CameraOptions& b) { return !(a == b); } +struct PropertyAnimation; + /** Various options for describing a transition between viewpoints with animation. All fields are optional; the default values depend on how this struct is used. */ @@ -156,4 +158,34 @@ struct FreeCameraOptions { void setPitchBearing(double pitch, double bearing) noexcept; }; +struct PropertyAnimation { + TimePoint start; + Duration duration; + AnimationOptions animation; + bool ran = false, finished = false, done = false; + bool panning = false, scaling = false, rotating = false; + + PropertyAnimation( + TimePoint start_, Duration duration_, AnimationOptions animation_, bool panning_, bool scaling_, bool rotating_) + : start(start_), + duration(duration_), + animation(animation_), + panning(panning_), + scaling(scaling_), + rotating(rotating_) {} + + double t(TimePoint now) { + bool isAnimated = duration != Duration::zero(); + double t = isAnimated ? (std::chrono::duration(now - start) / duration) : 1.0f; + if (t >= 1.0) { + return 1.0; + } + + util::UnitBezier ease = animation.easing ? *animation.easing : util::DEFAULT_TRANSITION_EASE; + return ease.solve(t, 0.001); + } + + bool isAnimated() const { return duration != Duration::zero(); } +}; + } // namespace mbgl diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index 06b4f29fd8e2..07c523db59e8 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,8 @@ class Map : private util::noncopyable { void setConstrainMode(ConstrainMode); void setViewportMode(ViewportMode); void setSize(Size); + void setFrustumOffset(const EdgeInsets&); + EdgeInsets getFrustumOffset(); MapOptions getMapOptions() const; // Projection Mode @@ -121,6 +124,7 @@ class Map : private util::noncopyable { // Transform TransformState getTransfromState() const; + void toggleTransform(); // Annotations void addAnnotationImage(std::unique_ptr); diff --git a/include/mbgl/mtl/buffer_resource.hpp b/include/mbgl/mtl/buffer_resource.hpp index 4efe37a9afdf..d519666f1293 100644 --- a/include/mbgl/mtl/buffer_resource.hpp +++ b/include/mbgl/mtl/buffer_resource.hpp @@ -87,6 +87,8 @@ class BufferResource { std::uint16_t version = 0; bool isIndexBuffer; bool persistent; + + mutable bool usingVertexBuffer = false; }; } // namespace mtl diff --git a/include/mbgl/mtl/render_pass.hpp b/include/mbgl/mtl/render_pass.hpp index c7a94821b183..9983135bf016 100644 --- a/include/mbgl/mtl/render_pass.hpp +++ b/include/mbgl/mtl/render_pass.hpp @@ -62,6 +62,7 @@ class RenderPass final : public gfx::RenderPass { void setCullMode(const MTL::CullMode); void setFrontFacingWinding(const MTL::Winding); + void setScissorRect(const MTL::ScissorRect); private: void pushDebugGroup(const char* name) override; @@ -92,6 +93,10 @@ class RenderPass final : public gfx::RenderPass { MTL::CullMode currentCullMode = MTL::CullModeNone; MTL::Winding currentWinding = MTL::WindingClockwise; + MTL::ScissorRect currentRect; + + size_t width; + size_t height; }; } // namespace mtl diff --git a/include/mbgl/shaders/gl/symbol_icon.hpp b/include/mbgl/shaders/gl/symbol_icon.hpp index 9ac73b34aa43..79590f9ff6ee 100644 --- a/include/mbgl/shaders/gl/symbol_icon.hpp +++ b/include/mbgl/shaders/gl/symbol_icon.hpp @@ -39,6 +39,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -115,7 +116,9 @@ lowp float opacity = u_opacity; 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/include/mbgl/shaders/gl/symbol_sdf.hpp b/include/mbgl/shaders/gl/symbol_sdf.hpp index 23a039491c06..cf0b7039b3e5 100644 --- a/include/mbgl/shaders/gl/symbol_sdf.hpp +++ b/include/mbgl/shaders/gl/symbol_sdf.hpp @@ -47,6 +47,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -167,7 +168,9 @@ lowp float halo_blur = u_halo_blur; 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/include/mbgl/shaders/gl/symbol_text_and_icon.hpp b/include/mbgl/shaders/gl/symbol_text_and_icon.hpp index 39fc1906b1f0..1d289c1bbb21 100644 --- a/include/mbgl/shaders/gl/symbol_text_and_icon.hpp +++ b/include/mbgl/shaders/gl/symbol_text_and_icon.hpp @@ -46,6 +46,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -166,7 +167,10 @@ lowp float halo_blur = u_halo_blur; 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } + float fontScale = size / 24.0; diff --git a/include/mbgl/shaders/mtl/symbol.hpp b/include/mbgl/shaders/mtl/symbol.hpp index c4f22e6677a7..d04194b5e9ed 100644 --- a/include/mbgl/shaders/mtl/symbol.hpp +++ b/include/mbgl/shaders/mtl/symbol.hpp @@ -29,19 +29,20 @@ struct alignas(16) SymbolDrawableUBO { /* 216 */ /*bool*/ int pitch_with_map; /* 220 */ /*bool*/ int is_size_zoom_constant; /* 224 */ /*bool*/ int is_size_feature_constant; + /* 228 */ /*bool*/ int is_offset; - /* 228 */ float size_t; - /* 232 */ float size; + /* 232 */ float size_t; + /* 236 */ float size; // Interpolations - /* 236 */ float fill_color_t; - /* 240 */ float halo_color_t; - /* 244 */ float opacity_t; - /* 248 */ float halo_width_t; - /* 252 */ float halo_blur_t; - /* 256 */ + /* 240 */ float fill_color_t; + /* 244 */ float halo_color_t; + /* 248 */ float opacity_t; + /* 252 */ float halo_width_t; + /* 256 */ float halo_blur_t; + /* 260 */ }; -static_assert(sizeof(SymbolDrawableUBO) == 16 * 16, "wrong size"); +static_assert(sizeof(SymbolDrawableUBO) == 17 * 16, "wrong size"); struct alignas(16) SymbolTilePropsUBO { /* 0 */ /*bool*/ int is_text; @@ -171,7 +172,9 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -348,7 +351,9 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -583,7 +588,9 @@ FragmentStage vertex vertexMain(thread const VertexStage vertx [[stage_in]], 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = size / 24.0; diff --git a/include/mbgl/shaders/symbol_layer_ubo.hpp b/include/mbgl/shaders/symbol_layer_ubo.hpp index 84f176d0dc63..63c4cb84ee4e 100644 --- a/include/mbgl/shaders/symbol_layer_ubo.hpp +++ b/include/mbgl/shaders/symbol_layer_ubo.hpp @@ -18,19 +18,20 @@ struct alignas(16) SymbolDrawableUBO { /* 216 */ /*bool*/ int pitch_with_map; /* 220 */ /*bool*/ int is_size_zoom_constant; /* 224 */ /*bool*/ int is_size_feature_constant; + /* 228 */ /*bool*/ int is_offset; - /* 228 */ float size_t; - /* 232 */ float size; + /* 232 */ float size_t; + /* 236 */ float size; // Interpolations - /* 236 */ float fill_color_t; - /* 240 */ float halo_color_t; - /* 244 */ float opacity_t; - /* 248 */ float halo_width_t; - /* 252 */ float halo_blur_t; - /* 256 */ + /* 240 */ float fill_color_t; + /* 244 */ float halo_color_t; + /* 248 */ float opacity_t; + /* 252 */ float halo_width_t; + /* 256 */ float halo_blur_t; + /* 260 */ }; -static_assert(sizeof(SymbolDrawableUBO) == 16 * 16); +static_assert(sizeof(SymbolDrawableUBO) == 17 * 16); struct alignas(16) SymbolTilePropsUBO { /* 0 */ /*bool*/ int is_text; diff --git a/include/mbgl/shaders/vulkan/symbol.hpp b/include/mbgl/shaders/vulkan/symbol.hpp index 96edd885d610..9a2c0dfad76b 100644 --- a/include/mbgl/shaders/vulkan/symbol.hpp +++ b/include/mbgl/shaders/vulkan/symbol.hpp @@ -52,6 +52,7 @@ struct SymbolDrawableUBO { bool pitch_with_map; bool is_size_zoom_constant; bool is_size_feature_constant; + bool is_offset; float size_t; float size; @@ -106,7 +107,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -256,6 +259,7 @@ struct SymbolDrawableUBO { bool pitch_with_map; bool is_size_zoom_constant; bool is_size_feature_constant; + bool is_offset; float size_t; float size; @@ -335,7 +339,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = drawable.is_text_prop ? size / 24.0 : size; @@ -563,6 +569,7 @@ struct SymbolDrawableUBO { bool pitch_with_map; bool is_size_zoom_constant; bool is_size_feature_constant; + bool is_offset; float size_t; float size; @@ -644,7 +651,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!drawable.is_offset) { + size *= perspective_ratio; + } const float fontScale = size / 24.0; diff --git a/include/mbgl/shaders/webgpu/symbol.hpp b/include/mbgl/shaders/webgpu/symbol.hpp index 557f22b6634f..fb0b3a9b0480 100644 --- a/include/mbgl/shaders/webgpu/symbol.hpp +++ b/include/mbgl/shaders/webgpu/symbol.hpp @@ -41,6 +41,7 @@ struct SymbolDrawableUBO { pitch_with_map: u32, is_size_zoom_constant: u32, is_size_feature_constant: u32, + is_offset: u32, size_t: f32, size: f32, @@ -175,7 +176,9 @@ fn main(in: VertexInput) -> VertexOutput { ); let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0); - size *= perspective_ratio; + if (drawable.is_offset == 0u) { + size *= perspective_ratio; + } let is_text = drawable.is_text_prop != 0u; let fontScale = select(size, size / 24.0, is_text); @@ -348,7 +351,9 @@ fn main(in: VertexInput) -> VertexOutput { ); let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0); - size *= perspective_ratio; + if (drawable.is_offset == 0u) { + size *= perspective_ratio; + } let is_text = drawable.is_text_prop != 0u; let fontScale = select(size, size / 24.0, is_text); @@ -597,7 +602,9 @@ fn main(in: VertexInput) -> VertexOutput { ); let perspective_ratio = clamp(0.5 + 0.5 * distance_ratio, 0.0, 4.0); - size *= perspective_ratio; + if (drawable.is_offset == 0u) { + size *= perspective_ratio; + } let fontScale = size / 24.0; diff --git a/include/mbgl/storage/file_source.hpp b/include/mbgl/storage/file_source.hpp index 6a70154df417..13626bf13103 100644 --- a/include/mbgl/storage/file_source.hpp +++ b/include/mbgl/storage/file_source.hpp @@ -26,9 +26,10 @@ enum FileSourceType : uint8_t { Network, Mbtiles, Pmtiles, - ResourceLoader ///< %Resource loader acts as a proxy and has logic + ResourceLoader, ///< %Resource loader acts as a proxy and has logic /// for request delegation to Asset, Cache, and other /// file sources. + Custom // These are the plugin file resource types }; // TODO: Rename to ResourceProvider to avoid confusion with diff --git a/include/mbgl/storage/file_source_manager.hpp b/include/mbgl/storage/file_source_manager.hpp index b6ef59c09b35..f6fe523e4260 100644 --- a/include/mbgl/storage/file_source_manager.hpp +++ b/include/mbgl/storage/file_source_manager.hpp @@ -43,6 +43,12 @@ class FileSourceManager { // a FileSourceType invocation has no effect. virtual FileSourceFactory unRegisterFileSourceFactory(FileSourceType) noexcept; + // Registers a custom file source + virtual void registerCustomFileSource(std::shared_ptr) noexcept; + + // Returns an array of custom file sources + virtual std::vector> getCustomFileSources() noexcept; + protected: FileSourceManager(); class Impl; diff --git a/include/mbgl/style/layers/custom_layer_render_parameters.hpp b/include/mbgl/style/layers/custom_layer_render_parameters.hpp index 54eb7d90d413..36be0aec0fbc 100644 --- a/include/mbgl/style/layers/custom_layer_render_parameters.hpp +++ b/include/mbgl/style/layers/custom_layer_render_parameters.hpp @@ -23,6 +23,7 @@ struct CustomLayerRenderParameters { double pitch; double fieldOfView; std::array projectionMatrix; + std::array nearClippedProjMatrix; CustomLayerRenderParameters(const PaintParameters&); }; diff --git a/include/mbgl/style/style.hpp b/include/mbgl/style/style.hpp index 0d2d45719158..f3e359c1b8b5 100644 --- a/include/mbgl/style/style.hpp +++ b/include/mbgl/style/style.hpp @@ -20,6 +20,7 @@ namespace style { class Light; class Source; class Layer; +class PluginStyleFilter; class Style { public: @@ -71,6 +72,9 @@ class Style { void addLayer(std::unique_ptr, const std::optional& beforeLayerID = std::nullopt); std::unique_ptr removeLayer(const std::string& layerID); + // Add style parsing filter + void addStyleFilter(std::shared_ptr); + // Private implementation class Impl; const std::unique_ptr impl; diff --git a/include/mbgl/style/transition_options.hpp b/include/mbgl/style/transition_options.hpp index 8dde72ec025f..517887edf125 100644 --- a/include/mbgl/style/transition_options.hpp +++ b/include/mbgl/style/transition_options.hpp @@ -4,6 +4,7 @@ #include +#include #include namespace mbgl { @@ -13,17 +14,23 @@ class TransitionOptions { public: std::optional duration; std::optional delay; + std::optional ease; bool enablePlacementTransitions; TransitionOptions(std::optional duration_ = std::nullopt, std::optional delay_ = std::nullopt, + std::optional ease_ = std::nullopt, bool enablePlacementTransitions_ = true) : duration(duration_ ? std::move(duration_) : std::nullopt), delay(delay_ ? std::move(delay_) : std::nullopt), + ease(ease_ ? std::move(ease_) : std::nullopt), enablePlacementTransitions(enablePlacementTransitions_) {} TransitionOptions reverseMerge(const TransitionOptions& defaults) const { - return {duration ? duration : defaults.duration, delay ? delay : defaults.delay, enablePlacementTransitions}; + return {duration ? duration : defaults.duration, + delay ? delay : defaults.delay, + ease ? ease : defaults.ease, + enablePlacementTransitions}; } bool isDefined() const { return duration || delay; } diff --git a/include/mbgl/util/unitbezier.hpp b/include/mbgl/util/unitbezier.hpp index 5b821b5a2130..074816bcad24 100644 --- a/include/mbgl/util/unitbezier.hpp +++ b/include/mbgl/util/unitbezier.hpp @@ -106,13 +106,13 @@ struct UnitBezier { } private: - const double cx; - const double bx; - const double ax; + double cx; + double bx; + double ax; - const double cy; - const double by; - const double ay; + double cy; + double by; + double ay; }; } // namespace util diff --git a/include/mbgl/vulkan/pipeline.hpp b/include/mbgl/vulkan/pipeline.hpp index 69c909e09767..aede451c83ec 100644 --- a/include/mbgl/vulkan/pipeline.hpp +++ b/include/mbgl/vulkan/pipeline.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -41,6 +42,8 @@ class PipelineInfo final { bool wideLines = false; + vk::Rect2D scissorRect; + // external values (used in hash) vk::RenderPass renderPass{}; vk::Extent2D viewExtent{}; @@ -67,6 +70,7 @@ class PipelineInfo final { static vk::CompareOp vulkanCompareOp(const gfx::DepthFunctionType& value); static vk::CompareOp vulkanCompareOp(const gfx::StencilFunctionType& value); static vk::StencilOp vulkanStencilOp(const gfx::StencilOpType& value); + static vk::Rect2D vulkanScissorRect(const gfx::ScissorRect& value); void setCullMode(const gfx::CullFaceMode& value); void setDrawMode(const gfx::DrawModeType& value); @@ -78,6 +82,7 @@ class PipelineInfo final { void setStencilMode(const gfx::StencilMode& value); void setRenderable(const gfx::Renderable& value); void setLineWidth(float value); + void setScissorRect(const gfx::ScissorRect& value); bool usesBlendConstants() const; void updateVertexInputHash(); diff --git a/metrics/.gitignore b/metrics/.gitignore new file mode 100644 index 000000000000..2d19fc766d98 --- /dev/null +++ b/metrics/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/platform/BUILD.bazel b/platform/BUILD.bazel index 1118b89f5e04..cd070cb58a08 100644 --- a/platform/BUILD.bazel +++ b/platform/BUILD.bazel @@ -37,6 +37,7 @@ objc_library( "CoreLocation", "AppKit", "SystemConfiguration", + "Accelerate", ], visibility = ["//visibility:public"], deps = [ @@ -161,6 +162,7 @@ objc_library( "CoreGraphics", "CoreLocation", "QuartzCore", + "Accelerate", ], deps = [ ":objc-headers", @@ -271,6 +273,10 @@ objc_library( "//platform/darwin:app/CustomStyleLayerExample.m", "//platform/darwin:app/PluginLayerExample.h", "//platform/darwin:app/PluginLayerExample.mm", + "//platform/darwin:app/PluginProtocolExample.h", + "//platform/darwin:app/PluginProtocolExample.mm", + "//platform/darwin:app/StyleFilterExample.h", + "//platform/darwin:app/StyleFilterExample.mm", "//platform/darwin:app/PluginLayerExampleMetalRendering.h", "//platform/darwin:app/PluginLayerExampleMetalRendering.mm", "//platform/ios:ios_app_srcs", diff --git a/platform/android/.gitignore b/platform/android/.gitignore index 14c4f869cb2a..f164c7f3145a 100644 --- a/platform/android/.gitignore +++ b/platform/android/.gitignore @@ -1 +1,7 @@ MapLibreAndroid/src/vulkanDebug +.project +.settings +.classpath +.kotlin +build/ +lint/ diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index 6d605f102ab6..959e42085a73 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog MapLibre Native for Android +## 12.0.2 + +### ✨ Features and improvements + +- Add listener to location update events from maplibre to the user + ## 12.0.1 ### ✨ Features and improvements @@ -34,6 +40,23 @@ - Add weak pointer handling ([#3763](https://github.com/maplibre/maplibre-native/pull/3763)). - Fix onSprite call on bad references ([#3805](https://github.com/maplibre/maplibre-native/pull/3805)). +## 11.13.4 + +### 🐞 Bug fixes + +- Avoid crashing when calling `toggleTransform` and `setFrustumOffset` if the map is destroyed. + +## 11.13.3 + +### ✨ Features and improvements + +- Added network delegate methods in iOS + +## 11.13.2 + +### ✨ Features and improvements + +- Sync with maplibre primary repo ## 11.13.1 @@ -46,6 +69,12 @@ - Fix Android backend cleanup ([#3681](https://github.com/maplibre/maplibre-native/pull/3681)). - Add weak pointer management to RasterSource and derived classes ([#3726](https://github.com/maplibre/maplibre-native/pull/3726)). +## 11.13.1 + +### Bug fixes + +- Fixed frustum offset in opengl + ## 11.13.0 ### ✨ Features and improvements @@ -65,6 +94,11 @@ ## 11.12.1 +### ✨ Features and improvements + +- Concurrent Camera Animations +- Ability to set out of bounds region to improve rendering + ### 🐞 Bug fixes - Revert "Fix the symbol blink issue by only placing the symbol in current level", as this was causing regressions ([#3610](https://github.com/maplibre/maplibre-native/pull/3610)). @@ -89,6 +123,18 @@ - Prevent `Style.validateState()` exception on location state updates ([#3574](https://github.com/maplibre/maplibre-native/pull/3574)). - Fix the symbol blink issue by only placing the symbol in current level ([#3534](https://github.com/maplibre/maplibre-native/pull/3534)). +## 11.10.5 + +### ✨ Features and improvements + +- Release debug builds for android + +## 11.10.4 + +### ✨ Features and improvements + +- Concurrent camera animations + ## 11.10.3 ### 🐞 Bug fixes @@ -160,6 +206,12 @@ We now make releases with debug builds to make it easier to report issues with r They are available with a `-debug` postfix on Maven Central, for example `org.maplibre.gl:android-sdk-vulkan-debug`. +## 11.8.7 + +### ✨ Features and improvements + +- Change Java Transfrom class from final to normal ([#3332](https://github.com/maplibre/maplibre-native/pull/3332)). + ## 11.8.6 ### ✨ Features and improvements @@ -171,6 +223,7 @@ They are available with a `-debug` postfix on Maven Central, for example `org.ma - Fix rare crash LatLngAnimator ([#3352](https://github.com/maplibre/maplibre-native/pull/3352)). - Sync surface destruction with main thread ([#3368](https://github.com/maplibre/maplibre-native/pull/3368)). - Prevent exception SymbolLocationLayerRenderer with new style ([#3369](https://github.com/maplibre/maplibre-native/pull/3369)). +- Fix issue related to symbol icon scaling with offset ## 11.8.5 @@ -272,6 +325,20 @@ Thanks to everyone who helped test the pre-releases! ### 🐞 Bug fixes - Fix crash on unsupported attribute type conversion ([#3066](https://github.com/maplibre/maplibre-native/pull/3066)). +## 11.6.3 + +## Features and improvements + +### Bug fixes + +- LoD Clamping fixed to be based on view range and not source range to support over/under zooming +- Disable depth writing for the location indicator layer + +## 11.6.2 + +### Features and Improvements + +- Add LoD Support ## 11.6.1 diff --git a/platform/android/Makefile b/platform/android/Makefile index ac4e8fdd7604..9cb4cc139ed6 100644 --- a/platform/android/Makefile +++ b/platform/android/Makefile @@ -246,7 +246,7 @@ run-android-test-app-center: # Uploads the compiled Android SDK to Maven Central Staging .PHONY: run-android-publish run-android-publish: - $(MLN_ANDROID_GRADLE_SINGLE_JOB)-Pmaplibre.abis=all :MapLibreAndroid:publishAllPublicationsToSonatypeRepository closeAndReleaseSonatypeStagingRepository + $(MLN_ANDROID_GRADLE_SINGLE_JOB) -Pmaplibre.abis=all publish # Dump system graphics information for the test app .PHONY: android-gfxinfo diff --git a/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp b/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp index b0a29b677d3d..24ab77e1b224 100644 --- a/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/jni_native.cpp @@ -37,6 +37,7 @@ #include "native_map_options.hpp" #include "rendering_stats.hpp" #include "util/tile_server_options.hpp" +#include "plugin/plugin_file_source.hpp" #ifndef MBGL_MODULE_OFFLINE_DISABLE #include "offline/offline_manager.hpp" #include "offline/offline_region.hpp" @@ -107,6 +108,11 @@ void registerNatives(JavaVM* vm) { Polygon::registerNative(env); Polyline::registerNative(env); + // Plugins + PluginProtocolHandlerResource::registerNative(env); + PluginProtocolHandlerResponse::registerNative(env); + PluginFileSource::registerNative(env); + // Map MapRenderer::registerNative(env); MapRendererRunnable::registerNative(env); diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp index bda6b12eb065..a9df1fc86165 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp @@ -29,6 +29,8 @@ #include #include #include +#include +#include // Java -> C++ conversion #include "style/android_conversion.hpp" @@ -929,6 +931,7 @@ void NativeMapView::setTransitionOptions(JNIEnv& env, const jni::ObjectgetStyle().setTransitionOptions(transitionOptions); } @@ -1320,6 +1323,106 @@ void NativeMapView::enableRenderingStatsView(JNIEnv&, jni::jboolean value) { map->enableRenderingStatsView(value); } +void NativeMapView::toggleTransform(JNIEnv&) { + assert(map); + map->toggleTransform(); +} + +void NativeMapView::setFrustumOffset(JNIEnv& env, const jni::Object& padding) { + mbgl::EdgeInsets offset = {RectF::getTop(env, padding), + RectF::getLeft(env, padding), + RectF::getBottom(env, padding), + RectF::getRight(env, padding)}; + map->setFrustumOffset(offset); +} + +// Plugins +void NativeMapView::addPluginFileSource(JNIEnv& jniEnv, const jni::Object& pluginFileSource) { + // TODO: Unclear if any of these options are needed for plugins + mbgl::ResourceOptions resourceOptions; + + // TODO: Unclear if any of the properties on clientOptions need to be set + mbgl::ClientOptions clientOptions; + + android::UniqueEnv _env = android::AttachEnv(); + + // Set when the source is added to a map. + jni::Global> pluginPeer = jni::NewGlobal(*_env, pluginFileSource); + std::shared_ptr fileSourceContainer = std::make_shared(); + fileSourceContainer->_fileSource = std::move(pluginPeer); + _pluginFileSources.push_back(fileSourceContainer); + + std::shared_ptr pluginSource = std::make_shared(resourceOptions, + clientOptions); + pluginSource->setOnRequestResourceFunction([fileSourceContainer](const mbgl::Resource& resource) -> mbgl::Response { + mbgl::Response tempResponse; + + android::UniqueEnv env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*env); + static auto requestResource = + javaClass.GetMethod(jni::Object)>( + *env, "requestResource"); + auto javaResource = PluginFileSource::createJavaResource(*env, resource); + auto tempResult = fileSourceContainer->_fileSource.Call(*env, requestResource, javaResource); + PluginProtocolHandlerResponse::Update(*env, tempResult, tempResponse); + + return tempResponse; + }); + pluginSource->setOnCanRequestFunction([fileSourceContainer](const mbgl::Resource& resource) -> bool { + android::UniqueEnv env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*env); + static auto canRequestResource = javaClass.GetMethod)>( + *env, "canRequestResource"); + auto javaResource = PluginFileSource::createJavaResource(*env, resource); + auto tempResult = fileSourceContainer->_fileSource.Call(*env, canRequestResource, javaResource); + return tempResult; + + /* + if (!renderingStats) { + renderingStats = jni::NewGlobal(*_env, RenderingStats::Create(*_env)); + } + + RenderingStats::Update(*_env, renderingStats, status.renderingStats); + + weakReference.Call(*_env, + onDidFinishRenderingFrame, + (jboolean)(status.mode != MapObserver::RenderMode::Partial), + renderingStats); + */ + /* + static auto resourceURLField = javaClass.GetField>(env, + "resource"); auto str = jni::Make(env, resource.url); // wrap the jstring javaObject.Set(env, + resourceURLField, str); + + fileSourceContainer->_fileSource.Set(env, ) + */ + }); + auto fileSourceManager = mbgl::FileSourceManager::get(); + fileSourceManager->registerCustomFileSource(pluginSource); + + /* + android::UniqueEnv _env = android::AttachEnv(); + static auto& javaClass = jni::Class::Singleton(*_env); + static auto onDidFinishRenderingFrame = javaClass.GetMethod)>( + *_env, "onDidFinishRenderingFrame"); + auto weakReference = javaPeer.get(*_env); + if (weakReference) { + if (!renderingStats) { + renderingStats = jni::NewGlobal(*_env, RenderingStats::Create(*_env)); + } + + RenderingStats::Update(*_env, renderingStats, status.renderingStats); + + weakReference.Call(*_env, + onDidFinishRenderingFrame, + (jboolean)(status.mode != MapObserver::RenderMode::Partial), + renderingStats); + } + + */ +} + // Static methods // void NativeMapView::registerNative(jni::JNIEnv& env) { @@ -1432,6 +1535,8 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getPrefetchZoomDelta, "nativeGetPrefetchZoomDelta"), METHOD(&NativeMapView::setTileCacheEnabled, "nativeSetTileCacheEnabled"), METHOD(&NativeMapView::getTileCacheEnabled, "nativeGetTileCacheEnabled"), + METHOD(&NativeMapView::isRenderingStatsViewEnabled, "nativeIsRenderingStatsViewEnabled"), + METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"), METHOD(&NativeMapView::setTileLodMinRadius, "nativeSetTileLodMinRadius"), METHOD(&NativeMapView::getTileLodMinRadius, "nativeGetTileLodMinRadius"), METHOD(&NativeMapView::setTileLodScale, "nativeSetTileLodScale"), @@ -1441,8 +1546,11 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::setTileLodZoomShift, "nativeSetTileLodZoomShift"), METHOD(&NativeMapView::getTileLodZoomShift, "nativeGetTileLodZoomShift"), METHOD(&NativeMapView::triggerRepaint, "nativeTriggerRepaint"), + METHOD(&NativeMapView::toggleTransform, "nativeToggleTransform"), + METHOD(&NativeMapView::setFrustumOffset, "nativeSetFrustumOffset"), METHOD(&NativeMapView::isRenderingStatsViewEnabled, "nativeIsRenderingStatsViewEnabled"), - METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView")); + METHOD(&NativeMapView::enableRenderingStatsView, "nativeEnableRenderingStatsView"), + METHOD(&NativeMapView::addPluginFileSource, "nativeAddPluginFileSource")); } void NativeMapView::onRegisterShaders(gfx::ShaderRegistry&) {}; diff --git a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp index 2497925cb15a..3bcbc4834fa6 100644 --- a/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp +++ b/platform/android/MapLibreAndroid/src/cpp/native_map_view.hpp @@ -25,7 +25,7 @@ #include "style/light.hpp" #include "native_map_options.hpp" #include "bitmap.hpp" - +#include "plugin/plugin_file_source.hpp" #include #include #include @@ -325,6 +325,13 @@ class NativeMapView : public MapObserver { jni::jboolean isRenderingStatsViewEnabled(JNIEnv&); void enableRenderingStatsView(JNIEnv&, jni::jboolean); + void toggleTransform(JNIEnv&); + + void setFrustumOffset(JNIEnv&, const jni::Object&); + + // Plugins + void addPluginFileSource(JNIEnv&, const jni::Object&); + // Shader compilation void onRegisterShaders(mbgl::gfx::ShaderRegistry&) override; void onPreCompileShader(mbgl::shaders::BuiltIn, mbgl::gfx::Backend::Type, const std::string&) override; @@ -366,6 +373,9 @@ class NativeMapView : public MapObserver { // Ensure these are initialised last std::unique_ptr map; + + // List of plugin file source + std::vector> _pluginFileSources; }; } // namespace android diff --git a/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp new file mode 100644 index 000000000000..b288678af17a --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp @@ -0,0 +1,78 @@ + +#include "plugin_file_source.hpp" + +using namespace mbgl::android; + +// Putting these here (should probably move out to a more dedicated JNI place within +// the native repo at some point, but this means we don't have to update +// the jni.hpp dependency +namespace jni { + +struct ByteBufferTag { + static constexpr auto Name() { return "java/nio/ByteBuffer"; } +}; + +template <> +struct TagTraits { + using SuperType = Object; + using UntaggedType = jobject; +}; + +} // namespace jni + +void PluginFileSource::registerNative(jni::JNIEnv& env) { + jni::Class::Singleton(env); +} + +jni::Global> PluginFileSource::createJavaResource( + jni::JNIEnv& env, const mbgl::Resource& resource) { + jni::Global> tempResult = jni::NewGlobal( + env, PluginProtocolHandlerResource::Create(env)); + PluginProtocolHandlerResource::Update(env, tempResult, resource); + return tempResult; +} + +void PluginProtocolHandlerResource::registerNative(jni::JNIEnv& env) { + jni::Class::Singleton(env); +} + +jni::Local> PluginProtocolHandlerResource::Create(jni::JNIEnv& env) { + auto& javaClass = jni::Class::Singleton(env); + auto constructor = javaClass.GetConstructor(env); + return javaClass.New(env, constructor); +} + +void PluginProtocolHandlerResource::Update(jni::JNIEnv& env, + jni::Object& javaObject, + const mbgl::Resource& resource) { + static auto& javaClass = jni::Class::Singleton(env); + + static auto resourceKindField = javaClass.GetField(env, "kind"); + javaObject.Set(env, resourceKindField, static_cast(resource.kind)); + + static auto resourceURLField = javaClass.GetField(env, "resourceURL"); + auto str = jni::Make(env, resource.url); // wrap the jstring + javaObject.Set(env, resourceURLField, str); +} + +void PluginProtocolHandlerResponse::registerNative(jni::JNIEnv& env) { + jni::Class::Singleton(env); +} + +jni::Local> PluginProtocolHandlerResponse::Create(jni::JNIEnv& env) { + auto& javaClass = jni::Class::Singleton(env); + auto constructor = javaClass.GetConstructor(env); + return javaClass.New(env, constructor); +} + +void PluginProtocolHandlerResponse::Update(jni::JNIEnv& env, + [[maybe_unused]] jni::Object& javaObject, + [[maybe_unused]] mbgl::Response& response) { + static auto& javaClass = jni::Class::Singleton(env); + static auto dataField = javaClass.GetField>(env, "data"); + auto objectValue = javaObject.Get(env, dataField); + auto objectRef = jobject(objectValue.get()); + void* bufPtr = env.GetDirectBufferAddress(objectRef); + jsize length = env.GetDirectBufferCapacity(objectRef); + response.data = std::make_shared((const char*)bufPtr, length); +} diff --git a/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.hpp b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.hpp new file mode 100644 index 000000000000..cea25e21e4de --- /dev/null +++ b/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace android { + +class PluginProtocolHandlerResource { +public: + static constexpr auto Name() { return "org/maplibre/android/plugin/PluginProtocolHandlerResource"; }; + static void registerNative(jni::JNIEnv &); + + static jni::Local> Create(jni::JNIEnv &); + static void Update(jni::JNIEnv &, jni::Object &, const mbgl::Resource &); +}; + +class PluginProtocolHandlerResponse { +public: + static constexpr auto Name() { return "org/maplibre/android/plugin/PluginProtocolHandlerResponse"; }; + static void registerNative(jni::JNIEnv &); + + static jni::Local> Create(jni::JNIEnv &); + static void Update(jni::JNIEnv &, jni::Object &, mbgl::Response &); +}; + +class PluginFileSource { +public: + static constexpr auto Name() { return "org/maplibre/android/plugin/PluginFileSource"; }; + + // static mbgl::PluginFileSource getFileSource(jni::JNIEnv&, const jni::Object&); + + static jni::Global> createJavaResource(jni::JNIEnv &env, + const mbgl::Resource &resource); + + static void registerNative(jni::JNIEnv &); +}; + +class PluginFileSourceContainer { +public: + jni::Global> _fileSource; +}; + +} // namespace android +} // namespace mbgl diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt index 1ccc441ffc10..ed5df3aa11a1 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/camera/CameraPosition.kt @@ -71,7 +71,7 @@ class CameraPosition */ @field:Keep @JvmField - val padding: DoubleArray? + val padding: DoubleArray?, ) : Parcelable { diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java index c923308079cf..58f29bb0b8a9 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationCameraController.java @@ -47,6 +47,8 @@ final class LocationCameraController { private LatLng lastLocation; private boolean isEnabled; + private boolean concurrentAnimations; + LocationCameraController( Context context, MapLibreMap maplibreMap, @@ -178,12 +180,14 @@ public void onFinish() { transform.moveCamera( maplibreMap, update, + true, callback); } else { transform.animateCamera( maplibreMap, update, (int) transitionDuration, + true, callback); } } else { @@ -202,7 +206,8 @@ private void setBearing(float bearing) { return; } - transform.moveCamera(maplibreMap, CameraUpdateFactory.bearingTo(bearing), null); + boolean shouldCancelTransitions = !(options.concurrentCameraAnimation() && isLocationTracking()); + transform.moveCamera(maplibreMap, CameraUpdateFactory.bearingTo(bearing), shouldCancelTransitions, null); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } @@ -210,8 +215,10 @@ private void setLatLng(@NonNull LatLng latLng) { if (isTransitioning) { return; } + lastLocation = latLng; - transform.moveCamera(maplibreMap, CameraUpdateFactory.newLatLng(latLng), null); + boolean shouldCancelTransitions = !(options.concurrentCameraAnimation() && isLocationTracking()); + transform.moveCamera(maplibreMap, CameraUpdateFactory.newLatLng(latLng), shouldCancelTransitions, null); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } @@ -220,7 +227,8 @@ private void setZoom(float zoom) { return; } - transform.moveCamera(maplibreMap, CameraUpdateFactory.zoomTo(zoom), null); + boolean shouldCancelTransitions = !(options.concurrentCameraAnimation() && isLocationTracking()); + transform.moveCamera(maplibreMap, CameraUpdateFactory.zoomTo(zoom), shouldCancelTransitions, null); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } @@ -229,7 +237,8 @@ private void setPadding(double[] padding) { return; } - transform.moveCamera(maplibreMap, CameraUpdateFactory.paddingTo(padding), null); + boolean shouldCancelTransitions = !(options.concurrentCameraAnimation() && isLocationTracking()); + transform.moveCamera(maplibreMap, CameraUpdateFactory.paddingTo(padding), true, null); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } @@ -238,7 +247,8 @@ private void setTilt(float tilt) { return; } - transform.moveCamera(maplibreMap, CameraUpdateFactory.tiltTo(tilt), null); + boolean shouldCancelTransitions = !(options.concurrentCameraAnimation() && isLocationTracking()); + transform.moveCamera(maplibreMap, CameraUpdateFactory.tiltTo(tilt), shouldCancelTransitions, null); onCameraMoveInvalidateListener.onInvalidateCameraMove(); } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponent.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponent.java index 5809d13f12af..c5ad38eb0716 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponent.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponent.java @@ -23,16 +23,16 @@ import org.maplibre.android.location.engine.LocationEngineDefault; import org.maplibre.android.location.engine.LocationEngineRequest; import org.maplibre.android.location.engine.LocationEngineResult; +import org.maplibre.android.location.engine.MapLibreFusedLocationEngineImpl; import org.maplibre.android.location.modes.CameraMode; import org.maplibre.android.location.modes.RenderMode; -import org.maplibre.android.location.engine.MapLibreFusedLocationEngineImpl; import org.maplibre.android.location.permissions.PermissionsManager; import org.maplibre.android.log.Logger; -import org.maplibre.android.maps.MapView; import org.maplibre.android.maps.MapLibreMap; import org.maplibre.android.maps.MapLibreMap.OnCameraIdleListener; import org.maplibre.android.maps.MapLibreMap.OnCameraMoveListener; import org.maplibre.android.maps.MapLibreMap.OnMapClickListener; +import org.maplibre.android.maps.MapView; import org.maplibre.android.maps.Style; import org.maplibre.android.maps.Transform; import org.maplibre.android.style.layers.SymbolLayer; @@ -53,7 +53,6 @@ import static org.maplibre.android.location.LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS; import static org.maplibre.android.location.modes.RenderMode.GPS; - /** * The Location Component provides location awareness to your mobile application. Enabling this * component provides a contextual experience to your users by showing an icon representing the users @@ -178,6 +177,8 @@ public final class LocationComponent { = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList onLocationLongClickListeners = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList onPuckPositionChangeListeners + = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList onCameraTrackingChangedListeners = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList onRenderModeChangedListeners @@ -990,6 +991,24 @@ public void removeOnLocationLongClickListener(@NonNull OnLocationLongClickListen onLocationLongClickListeners.remove(listener); } + /** + * Adds a listener that receives updates whenever the rendered puck position changes. + * + * @param listener listener that will be invoked on puck position changes + */ + public void addOnPuckPositionChangeListener(@NonNull OnPuckPositionChangeListener listener) { + onPuckPositionChangeListeners.add(listener); + } + + /** + * Removes a listener that was receiving puck position updates. + * + * @param listener listener to remove + */ + public void removeOnPuckPositionChangeListener(@NonNull OnPuckPositionChangeListener listener) { + onPuckPositionChangeListeners.remove(listener); + } + /** * Adds a listener that gets invoked when camera tracking state changes. * @@ -1298,6 +1317,12 @@ private void updateLocation(@Nullable final Location location, @Nullable List intermediatePoints) { Location[] locations = new Location[intermediatePoints.size() + 1]; locations[locations.length - 1] = location; @@ -1379,7 +1404,13 @@ private void updateAccuracyRadius(Location location, boolean noAnimation) { private void updateAnimatorListenerHolders() { Set animationsValueChangeListeners = new HashSet<>(); - animationsValueChangeListeners.addAll(locationLayerController.getAnimationListeners()); + for (AnimatorListenerHolder holder : locationLayerController.getAnimationListeners()) { + if (holder.getAnimatorType() == MapLibreAnimator.ANIMATOR_LAYER_LATLNG) { + animationsValueChangeListeners.add(wrapPuckPositionListener(holder)); + } else { + animationsValueChangeListeners.add(holder); + } + } animationsValueChangeListeners.addAll(locationCameraController.getAnimationListeners()); locationAnimatorCoordinator.updateAnimatorListenerHolders(animationsValueChangeListeners); locationAnimatorCoordinator.resetAllCameraAnimations(maplibreMap.getCameraPosition(), @@ -1387,6 +1418,25 @@ private void updateAnimatorListenerHolders() { locationAnimatorCoordinator.resetAllLayerAnimations(); } + @SuppressWarnings("unchecked") + private AnimatorListenerHolder wrapPuckPositionListener(AnimatorListenerHolder originalHolder) { + // Forward animator ticks to both the renderer and public puck listeners. + final MapLibreAnimator.AnimationsValueChangeListener originalListener = + (MapLibreAnimator.AnimationsValueChangeListener) originalHolder.getListener(); + if (originalListener == null) { + return originalHolder; + } + MapLibreAnimator.AnimationsValueChangeListener compositeListener = + new MapLibreAnimator.AnimationsValueChangeListener() { + @Override + public void onNewAnimationValue(LatLng value) { + originalListener.onNewAnimationValue(value); + notifyPuckPositionChange(value); + } + }; + return new AnimatorListenerHolder(MapLibreAnimator.ANIMATOR_LAYER_LATLNG, compositeListener); + } + @NonNull private OnCameraMoveListener onCameraMoveListener = new OnCameraMoveListener() { @Override @@ -1546,7 +1596,7 @@ public void onRenderModeChanged(int currentMode) { new MapLibreMap.OnDeveloperAnimationListener() { @Override public void onDeveloperAnimationStarted() { - if (isComponentInitialized && isEnabled) { + if (isComponentInitialized && isEnabled && !options.concurrentCameraAnimation()) { setCameraMode(CameraMode.NONE); } } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponentOptions.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponentOptions.java index 83b6db8a282e..2a89be06ac24 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponentOptions.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationComponentOptions.java @@ -144,6 +144,7 @@ public class LocationComponentOptions implements Parcelable { private float pulseAlpha; @Nullable private Interpolator pulseInterpolator; + private Boolean concurrentCameraAnimation; public LocationComponentOptions( float accuracyAlpha, @@ -186,7 +187,8 @@ public LocationComponentOptions( float pulseSingleDuration, float pulseMaxRadius, float pulseAlpha, - @Nullable Interpolator pulseInterpolator) { + @Nullable Interpolator pulseInterpolator, + Boolean concurrentCameraAnimation) { this.accuracyAlpha = accuracyAlpha; this.accuracyColor = accuracyColor; this.backgroundDrawableStale = backgroundDrawableStale; @@ -231,6 +233,7 @@ public LocationComponentOptions( this.pulseMaxRadius = pulseMaxRadius; this.pulseAlpha = pulseAlpha; this.pulseInterpolator = pulseInterpolator; + this.concurrentCameraAnimation = concurrentCameraAnimation; } /** @@ -373,6 +376,9 @@ public static LocationComponentOptions createFromAttributes(@NonNull Context con builder.pulseAlpha = typedArray.getFloat( R.styleable.maplibre_LocationComponent_maplibre_pulsingLocationCircleAlpha, CIRCLE_PULSING_ALPHA_DEFAULT); + builder.concurrentCameraAnimation = typedArray.getBoolean( + R.styleable.maplibre_LocationComponent_maplibre_concurrentCameraAnimation, false); + typedArray.recycle(); return builder.build(); @@ -892,6 +898,10 @@ public Interpolator pulseInterpolator() { return pulseInterpolator; } + public Boolean concurrentCameraAnimation() { + return concurrentCameraAnimation; + } + @NonNull @Override public String toString() { @@ -934,6 +944,7 @@ public String toString() { + "pulseSingleDuration=" + pulseSingleDuration + "pulseMaxRadius=" + pulseMaxRadius + "pulseAlpha=" + pulseAlpha + + "concurrentCameraAnimation=" + concurrentCameraAnimation + "}"; } @@ -1081,6 +1092,10 @@ public boolean equals(Object o) { return false; } + if (concurrentCameraAnimation != options.concurrentCameraAnimation) { + return false; + } + return layerBelow != null ? layerBelow.equals(options.layerBelow) : options.layerBelow == null; } @@ -1130,6 +1145,7 @@ public int hashCode() { result = 31 * result + (pulseSingleDuration != +0.0f ? Float.floatToIntBits(pulseSingleDuration) : 0); result = 31 * result + (pulseMaxRadius != +0.0f ? Float.floatToIntBits(pulseMaxRadius) : 0); result = 31 * result + (pulseAlpha != +0.0f ? Float.floatToIntBits(pulseAlpha) : 0); + result = 31 * result + (concurrentCameraAnimation ? 1 : 0); return result; } @@ -1180,6 +1196,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeFloat(this.pulseSingleDuration); dest.writeFloat(this.pulseMaxRadius); dest.writeFloat(this.pulseAlpha); + dest.writeByte(this.concurrentCameraAnimation ? (byte) 1 : (byte) 0); } protected LocationComponentOptions(Parcel in) { @@ -1223,6 +1240,7 @@ protected LocationComponentOptions(Parcel in) { this.pulseSingleDuration = in.readFloat(); this.pulseMaxRadius = in.readFloat(); this.pulseAlpha = in.readFloat(); + this.concurrentCameraAnimation = in.readByte() != 0; } public static final Parcelable.Creator CREATOR = @@ -1349,6 +1367,7 @@ public LocationComponentOptions build() { private float pulseAlpha; @Nullable private Interpolator pulseInterpolator; + private Boolean concurrentCameraAnimation; Builder() { } @@ -1395,6 +1414,7 @@ private Builder(LocationComponentOptions source) { this.pulseMaxRadius = source.pulseMaxRadius; this.pulseAlpha = source.pulseAlpha; this.pulseInterpolator = source.pulseInterpolator; + this.concurrentCameraAnimation = source.concurrentCameraAnimation; } /** @@ -1973,6 +1993,11 @@ public LocationComponentOptions.Builder pulseInterpolator(Interpolator pulseInte return this; } + public LocationComponentOptions.Builder concurrentCameraAnimation(Boolean concurrentCameraAnimation) { + this.concurrentCameraAnimation = concurrentCameraAnimation; + return this; + } + @Nullable LocationComponentOptions autoBuild() { String missing = ""; @@ -2074,7 +2099,8 @@ LocationComponentOptions autoBuild() { this.pulseSingleDuration, this.pulseMaxRadius, this.pulseAlpha, - this.pulseInterpolator); + this.pulseInterpolator, + this.concurrentCameraAnimation); } } } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationLayerController.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationLayerController.java index a1039c12e652..9459d3ef2de1 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationLayerController.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/LocationLayerController.java @@ -1,37 +1,36 @@ package org.maplibre.android.location; +import static org.maplibre.android.location.LocationComponentConstants.BACKGROUND_ICON; +import static org.maplibre.android.location.LocationComponentConstants.BACKGROUND_LAYER; +import static org.maplibre.android.location.LocationComponentConstants.BACKGROUND_STALE_ICON; +import static org.maplibre.android.location.LocationComponentConstants.BEARING_ICON; +import static org.maplibre.android.location.LocationComponentConstants.BEARING_LAYER; +import static org.maplibre.android.location.LocationComponentConstants.FOREGROUND_ICON; +import static org.maplibre.android.location.LocationComponentConstants.FOREGROUND_LAYER; +import static org.maplibre.android.location.LocationComponentConstants.FOREGROUND_STALE_ICON; +import static org.maplibre.android.style.expressions.Expression.interpolate; +import static org.maplibre.android.style.expressions.Expression.linear; +import static org.maplibre.android.style.expressions.Expression.stop; +import static org.maplibre.android.style.expressions.Expression.zoom; + import android.graphics.Bitmap; import android.graphics.PointF; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.maplibre.geojson.Feature; +import org.maplibre.android.geometry.LatLng; import org.maplibre.android.location.modes.RenderMode; import org.maplibre.android.log.Logger; import org.maplibre.android.maps.MapLibreMap; import org.maplibre.android.maps.Style; import org.maplibre.android.style.expressions.Expression; +import org.maplibre.geojson.Feature; import java.util.HashSet; import java.util.List; import java.util.Set; -import static org.maplibre.android.location.LocationComponentConstants.BACKGROUND_ICON; -import static org.maplibre.android.location.LocationComponentConstants.BACKGROUND_LAYER; -import static org.maplibre.android.location.LocationComponentConstants.BACKGROUND_STALE_ICON; -import static org.maplibre.android.location.LocationComponentConstants.BEARING_ICON; -import static org.maplibre.android.location.LocationComponentConstants.BEARING_LAYER; -import static org.maplibre.android.location.LocationComponentConstants.FOREGROUND_ICON; -import static org.maplibre.android.location.LocationComponentConstants.FOREGROUND_LAYER; -import static org.maplibre.android.location.LocationComponentConstants.FOREGROUND_STALE_ICON; -import static org.maplibre.android.style.expressions.Expression.interpolate; -import static org.maplibre.android.style.expressions.Expression.linear; -import static org.maplibre.android.style.expressions.Expression.stop; -import static org.maplibre.android.style.expressions.Expression.zoom; - -import org.maplibre.android.geometry.LatLng; - final class LocationLayerController { private static final String TAG = "Mbgl-LocationLayerController"; diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/OnPuckPositionChangeListener.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/OnPuckPositionChangeListener.java new file mode 100644 index 000000000000..e25cc57aa563 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/location/OnPuckPositionChangeListener.java @@ -0,0 +1,17 @@ +package org.maplibre.android.location; + +import androidx.annotation.NonNull; + +import org.maplibre.android.geometry.LatLng; + +/** + * Listener invoked whenever the rendered puck position changes. + */ +public interface OnPuckPositionChangeListener { + /** + * Called when the puck position is updated as part of the location animation. + * + * @param puckPosition current rendered puck position. + */ + void onPuckPositionChanged(@NonNull LatLng puckPosition); +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java index cdbecdfd660a..3abcfd90d050 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapLibreMap.java @@ -20,6 +20,7 @@ import org.maplibre.android.gestures.RotateGestureDetector; import org.maplibre.android.gestures.ShoveGestureDetector; import org.maplibre.android.gestures.StandardScaleGestureDetector; +import org.maplibre.android.plugin.PluginProtocolHandler; import org.maplibre.geojson.Feature; import org.maplibre.geojson.Geometry; import org.maplibre.android.MapStrictMode; @@ -117,6 +118,14 @@ public void enableRenderingStatsView(boolean value) { nativeMapView.enableRenderingStatsView(value); } + public void toggleTransform() { + nativeMapView.toggleTransform(); + } + + public void setFrustumOffset(@NonNull RectF offset) { + nativeMapView.setFrustumOffset(offset); + } + public void setSwapBehaviorFlush(boolean flush) { nativeMapView.setSwapBehaviorFlush(flush); } @@ -633,7 +642,7 @@ public final CameraPosition getCameraPosition() { * @param cameraPosition the camera position to set */ public void setCameraPosition(@NonNull CameraPosition cameraPosition) { - moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), null); + moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition), true, null); } /** @@ -644,7 +653,12 @@ public void setCameraPosition(@NonNull CameraPosition cameraPosition) { * @param update The change that should be applied to the camera. */ public final void moveCamera(@NonNull CameraUpdate update) { - moveCamera(update, null); + moveCamera(update, true, null); + } + + public final void moveCamera(@NonNull final CameraUpdate update, + @Nullable final MapLibreMap.CancelableCallback callback) { + moveCamera(update, true, callback); } /** @@ -656,9 +670,10 @@ public final void moveCamera(@NonNull CameraUpdate update) { * @param callback the callback to be invoked when an animation finishes or is canceled */ public final void moveCamera(@NonNull final CameraUpdate update, + @NonNull final boolean shouldCancelTransitions, @Nullable final MapLibreMap.CancelableCallback callback) { notifyDeveloperAnimationListeners(); - transform.moveCamera(MapLibreMap.this, update, callback); + transform.moveCamera(MapLibreMap.this, update, shouldCancelTransitions, callback); } /** @@ -743,7 +758,12 @@ public final void easeCamera(@NonNull CameraUpdate update, int durationMs, * @param easingInterpolator True for easing interpolator, false for linear. */ public final void easeCamera(@NonNull CameraUpdate update, int durationMs, boolean easingInterpolator) { - easeCamera(update, durationMs, easingInterpolator, null); + easeCamera(update, durationMs, easingInterpolator, true, null); + } + + public final void easeCamera(@NonNull CameraUpdate update, int durationMs, boolean easingInterpolator, + @Nullable final MapLibreMap.CancelableCallback callback) { + easeCamera(update, durationMs, easingInterpolator, true, callback); } /** @@ -765,12 +785,14 @@ public final void easeCamera(@NonNull CameraUpdate update, int durationMs, boole public final void easeCamera(@NonNull final CameraUpdate update, final int durationMs, final boolean easingInterpolator, + final boolean shouldCancelTransitions, @Nullable final MapLibreMap.CancelableCallback callback) { if (durationMs <= 0) { throw new IllegalArgumentException("Null duration passed into easeCamera"); } notifyDeveloperAnimationListeners(); - transform.easeCamera(MapLibreMap.this, update, durationMs, easingInterpolator, callback); + + transform.easeCamera(MapLibreMap.this, update, durationMs, easingInterpolator, shouldCancelTransitions, callback); } /** @@ -783,7 +805,7 @@ public final void easeCamera(@NonNull final CameraUpdate update, * @see CameraUpdateFactory for a set of updates. */ public final void animateCamera(@NonNull CameraUpdate update) { - animateCamera(update, MapLibreConstants.ANIMATION_DURATION, null); + animateCamera(update, MapLibreConstants.ANIMATION_DURATION, true, null); } /** @@ -799,7 +821,7 @@ public final void animateCamera(@NonNull CameraUpdate update) { * @see CameraUpdateFactory for a set of updates. */ public final void animateCamera(@NonNull CameraUpdate update, @Nullable MapLibreMap.CancelableCallback callback) { - animateCamera(update, MapLibreConstants.ANIMATION_DURATION, callback); + animateCamera(update, MapLibreConstants.ANIMATION_DURATION, true, callback); } /** @@ -814,7 +836,12 @@ public final void animateCamera(@NonNull CameraUpdate update, @Nullable MapLibre * @see CameraUpdateFactory for a set of updates. */ public final void animateCamera(@NonNull CameraUpdate update, int durationMs) { - animateCamera(update, durationMs, null); + animateCamera(update, durationMs, true, null); + } + + public final void animateCamera(@NonNull CameraUpdate update, int durationMs, + @Nullable MapLibreMap.CancelableCallback callback) { + animateCamera(update, durationMs, true, callback); } /** @@ -836,12 +863,13 @@ public final void animateCamera(@NonNull CameraUpdate update, int durationMs) { * @see CameraUpdateFactory for a set of updates. */ public final void animateCamera(@NonNull final CameraUpdate update, final int durationMs, + final boolean shouldCancelTransitions, @Nullable final MapLibreMap.CancelableCallback callback) { if (durationMs <= 0) { throw new IllegalArgumentException("Null duration passed into animateCamera"); } notifyDeveloperAnimationListeners(); - transform.animateCamera(MapLibreMap.this, update, durationMs, callback); + transform.animateCamera(MapLibreMap.this, update, durationMs, shouldCancelTransitions, callback); } /** @@ -2694,4 +2722,18 @@ private void notifyDeveloperAnimationListeners() { listener.onDeveloperAnimationStarted(); } } + + + + /** + * Adds a custom protocol handler to the map view + */ + ArrayList pluginProtocolHandlers = new ArrayList(); + + public void addPluginProtocolHandler(PluginProtocolHandler protocolHandler) { + pluginProtocolHandlers.add(protocolHandler); + nativeMapView.addPluginProtocolHandler(protocolHandler); + } + + } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java index d0828b14272f..7673241ec043 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/MapView.java @@ -31,6 +31,7 @@ import org.maplibre.android.maps.renderer.MapRenderer; import org.maplibre.android.maps.widgets.CompassView; import org.maplibre.android.net.ConnectivityReceiver; +import org.maplibre.android.plugin.PluginProtocolHandler; import org.maplibre.android.storage.FileSource; import org.maplibre.android.utils.BitmapUtils; import org.maplibre.android.tile.TileOperation; @@ -1817,4 +1818,14 @@ private AttributionDialogManager getDialogManager() { public static void setMapStrictModeEnabled(boolean strictModeEnabled) { MapStrictMode.setStrictModeEnabled(strictModeEnabled); } + + /** + * Adds a custom protocol handler to the map view + */ + public void addPluginProtocolHandler(PluginProtocolHandler protocolHandler) { + if (maplibreMap != null) { + maplibreMap.addPluginProtocolHandler(protocolHandler); + } + } + } diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java index 585f7ed332b6..078376485ca3 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMap.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.maplibre.android.plugin.PluginProtocolHandler; import org.maplibre.geojson.Feature; import org.maplibre.geojson.Geometry; import org.maplibre.android.annotations.Marker; @@ -270,6 +271,12 @@ List queryRenderedFeatures(@NonNull RectF coordinates, void enableRenderingStatsView(boolean value); + void toggleTransform(); + + void setFrustumOffset(RectF offset); + + void addPluginProtocolHandler(PluginProtocolHandler protocolHandler); + void setSwapBehaviorFlush(boolean flush); // diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java index 571193bd45d5..78f624f8dab7 100755 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/NativeMapView.java @@ -13,6 +13,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.maplibre.android.plugin.PluginFileSource; +import org.maplibre.android.plugin.PluginProtocolHandler; import org.maplibre.geojson.Feature; import org.maplibre.geojson.Geometry; import org.maplibre.android.LibraryLoader; @@ -1149,6 +1151,28 @@ public void enableRenderingStatsView(boolean value) { nativeEnableRenderingStatsView(value); } + @Override + public void toggleTransform() { + if (checkState("toggleTransform")) { + return; + } + nativeToggleTransform(); + } + + @Override + public void setFrustumOffset(RectF offset) { + if (checkState("setFrustumOffset")) { + return; + } + nativeSetFrustumOffset(offset); + } + + public void addPluginProtocolHandler(PluginProtocolHandler protocolHandler) { + PluginFileSource pluginFileSource = new PluginFileSource(); + pluginFileSource.protocolHandler = protocolHandler; + nativeAddPluginFileSource(pluginFileSource); + } + @Override public void setSwapBehaviorFlush(boolean flush) { mapRenderer.setSwapBehaviorFlush(flush); @@ -1746,6 +1770,16 @@ public long getNativePtr() { @Keep private native void nativeEnableRenderingStatsView(boolean enabled); + @Keep + private native void nativeToggleTransform(); + + @Keep + private native void nativeSetFrustumOffset(RectF offsset); + + @Keep + private native void nativeAddPluginFileSource(PluginFileSource fileSource); + + // // Snapshot // diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/Transform.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/Transform.java index 3efc3c254df7..040e6e0c6ba5 100644 --- a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/Transform.java +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/Transform.java @@ -59,7 +59,7 @@ public void onCameraDidChange(boolean animated) { void initialise(@NonNull MapLibreMap maplibreMap, @NonNull MapLibreMapOptions options) { CameraPosition position = options.getCamera(); if (position != null && !position.equals(CameraPosition.DEFAULT)) { - moveCamera(maplibreMap, CameraUpdateFactory.newCameraPosition(position), null); + moveCamera(maplibreMap, CameraUpdateFactory.newCameraPosition(position), true, null); } setMinZoom(options.getMinZoomPreference()); setMaxZoom(options.getMaxZoomPreference()); @@ -102,15 +102,23 @@ public void run() { } } + @UiThread + public void moveCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, + @Nullable final MapLibreMap.CancelableCallback callback) { + moveCamera(maplibreMap, update, true, callback); + } + /** * Internal use. */ @UiThread - public void moveCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, + public void moveCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, boolean shouldCancelTransitions, @Nullable final MapLibreMap.CancelableCallback callback) { CameraPosition cameraPosition = update.getCameraPosition(maplibreMap); if (isValidCameraPosition(cameraPosition)) { - cancelTransitions(); + if (shouldCancelTransitions) { + cancelTransitions(); + } cameraChangeDispatcher.onCameraMoveStarted(OnCameraMoveStartedListener.REASON_API_ANIMATION); nativeMap.jumpTo(cameraPosition.target, cameraPosition.zoom, cameraPosition.tilt, cameraPosition.bearing, cameraPosition.padding); @@ -131,11 +139,20 @@ public void run() { @UiThread void easeCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, int durationMs, - boolean easingInterpolator, + boolean easingInterpolator, + @Nullable final MapLibreMap.CancelableCallback callback) { + easeCamera(maplibreMap, update, durationMs, easingInterpolator, true, callback); + } + + @UiThread + void easeCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, int durationMs, + boolean easingInterpolator, boolean shouldCancelTransitions, @Nullable final MapLibreMap.CancelableCallback callback) { CameraPosition cameraPosition = update.getCameraPosition(maplibreMap); if (isValidCameraPosition(cameraPosition)) { - cancelTransitions(); + if (shouldCancelTransitions) { + cancelTransitions(); + } cameraChangeDispatcher.onCameraMoveStarted(OnCameraMoveStartedListener.REASON_API_ANIMATION); if (callback != null) { @@ -149,15 +166,24 @@ void easeCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, int durat } } + @UiThread + public void animateCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, int durationMs, + @Nullable final MapLibreMap.CancelableCallback callback) { + animateCamera(maplibreMap, update, durationMs, true, callback); + } + /** * Internal use. */ @UiThread public void animateCamera(@NonNull MapLibreMap maplibreMap, CameraUpdate update, int durationMs, + boolean shouldCancelTransitions, @Nullable final MapLibreMap.CancelableCallback callback) { CameraPosition cameraPosition = update.getCameraPosition(maplibreMap); if (isValidCameraPosition(cameraPosition)) { - cancelTransitions(); + if (shouldCancelTransitions) { + cancelTransitions(); + } cameraChangeDispatcher.onCameraMoveStarted(OnCameraMoveStartedListener.REASON_API_ANIMATION); if (callback != null) { diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginFileSource.java b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginFileSource.java new file mode 100644 index 000000000000..9321740f0a7c --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginFileSource.java @@ -0,0 +1,20 @@ +package org.maplibre.android.plugin; + +public class PluginFileSource { + + public PluginProtocolHandler protocolHandler; + + public boolean canRequestResource(PluginProtocolHandlerResource resource) { + + return protocolHandler.canRequestResource(resource); + + } + + public PluginProtocolHandlerResponse requestResource(PluginProtocolHandlerResource resource) { + + PluginProtocolHandlerResponse response = protocolHandler.requestResource(resource); + return response; + + } + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandler.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandler.kt new file mode 100644 index 000000000000..2b84385a9d2a --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandler.kt @@ -0,0 +1,18 @@ +package org.maplibre.android.plugin + +import android.icu.text.Normalizer + +public open class PluginProtocolHandler { + + open fun canRequestResource(resource: PluginProtocolHandlerResource?): Boolean { + // Base class does nothing + return false; + } + + + open fun requestResource(resource: PluginProtocolHandlerResource?): PluginProtocolHandlerResponse? { + // Base class does nothing + return null; + } + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResource.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResource.kt new file mode 100644 index 000000000000..606311cdf690 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResource.kt @@ -0,0 +1,33 @@ +package org.maplibre.android.plugin + +class PluginProtocolHandlerResource { + + enum class Kind { + Unknown, + Style, + Source, + Tile, + Glyphs, + SpriteImage, + SpriteJSON, + Image + }; + + enum class LoadingMethod { + Unknown, + CacheOnly, + NetworkOnly, + All + }; + + var resourceURL: String = ""; + + var kind: Int = 0; + + var resourceKind: Kind = Kind.Unknown; + + var loadingMethod: LoadingMethod = LoadingMethod.Unknown; + + var tileData: TileData? = null; + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResponse.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResponse.kt new file mode 100644 index 000000000000..5df047bd7c51 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/PluginProtocolHandlerResponse.kt @@ -0,0 +1,18 @@ +package org.maplibre.android.plugin + +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets + +class PluginProtocolHandlerResponse { + + fun generateBuffer(stringBuffer: String) { + val byteArray = stringBuffer.toByteArray(StandardCharsets.UTF_8); + val buffer = ByteBuffer.allocateDirect(byteArray.size); + buffer.put(byteArray); + buffer.flip(); + data = buffer; + } + + var data: ByteBuffer? = null; + +} diff --git a/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/TileData.kt b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/TileData.kt new file mode 100644 index 000000000000..2a9f27abe151 --- /dev/null +++ b/platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/plugin/TileData.kt @@ -0,0 +1,15 @@ +package org.maplibre.android.plugin + +class TileData { + + var tileURLTemplate: String? = null; + + var tilePixelRatio: Int = 0; + + var tileX: Int = 0; + + var tileY: Int = 0; + + var tileZoom: Int = 0; + +} diff --git a/platform/android/MapLibreAndroid/src/main/res-public/values/public.xml b/platform/android/MapLibreAndroid/src/main/res-public/values/public.xml index bd4ea5ee290a..ffe7279836a7 100644 --- a/platform/android/MapLibreAndroid/src/main/res-public/values/public.xml +++ b/platform/android/MapLibreAndroid/src/main/res-public/values/public.xml @@ -173,4 +173,7 @@ + + + diff --git a/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml b/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml index eb367bd917f8..00df355ce06e 100644 --- a/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml +++ b/platform/android/MapLibreAndroid/src/main/res/values/attrs.xml @@ -24,6 +24,7 @@ + @@ -203,5 +204,8 @@ + + + diff --git a/platform/android/MapLibreAndroid/src/main/res/values/styles.xml b/platform/android/MapLibreAndroid/src/main/res/values/styles.xml index 38459eb3a70c..ff5fcd2e7096 100644 --- a/platform/android/MapLibreAndroid/src/main/res/values/styles.xml +++ b/platform/android/MapLibreAndroid/src/main/res/values/styles.xml @@ -42,5 +42,8 @@ 0.4 decelerate + + false + diff --git a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt index bc4dfad78e95..61bf9d5cfa1f 100644 --- a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt +++ b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/location/LocationCameraControllerTest.kt @@ -264,6 +264,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -290,6 +291,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -316,6 +318,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -342,6 +345,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -369,6 +373,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -417,6 +422,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) val uiSettings = Mockito.mock(UiSettings::class.java) @@ -447,6 +453,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -473,6 +480,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -502,6 +510,7 @@ class LocationCameraControllerTest { ArgumentMatchers.any( CameraUpdate::class.java ), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -531,6 +540,7 @@ class LocationCameraControllerTest { ArgumentMatchers.any( CameraUpdate::class.java ), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -558,6 +568,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -585,6 +596,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -612,6 +624,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -639,6 +652,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -665,6 +679,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -691,6 +706,7 @@ class LocationCameraControllerTest { MapLibreMap::class.java ), ArgumentMatchers.any(CameraUpdate::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -1078,6 +1094,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1118,6 +1135,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1161,6 +1179,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) camera.setCameraMode( @@ -1180,6 +1199,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1252,6 +1272,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1304,6 +1325,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) camera.setCameraMode( @@ -1324,6 +1346,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1367,6 +1390,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) camera.setCameraMode( @@ -1386,6 +1410,7 @@ class LocationCameraControllerTest { CameraUpdate::class.java ), ArgumentMatchers.any(Int::class.java), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1441,6 +1466,7 @@ class LocationCameraControllerTest { ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.eq(newCameraPosition(builder.build())), ArgumentMatchers.eq(LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS.toInt()), + ArgumentMatchers.any(Boolean::class.java), callbackCaptor.capture() ) Assert.assertTrue(camera.isTransitioning) @@ -1499,6 +1525,7 @@ class LocationCameraControllerTest { Mockito.verify(transform).moveCamera( ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.eq(newCameraPosition(builder.build())), + ArgumentMatchers.any(Boolean::class.java), callbackCaptor.capture() ) Assert.assertTrue(camera.isTransitioning) @@ -1558,6 +1585,7 @@ class LocationCameraControllerTest { ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.eq(newCameraPosition(builder.build())), ArgumentMatchers.eq(LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS.toInt()), + ArgumentMatchers.any(Boolean::class.java), callbackCaptor.capture() ) Assert.assertTrue(camera.isTransitioning) @@ -1614,6 +1642,7 @@ class LocationCameraControllerTest { ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.eq(newCameraPosition(builder.build())), ArgumentMatchers.eq(LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS.toInt()), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1666,6 +1695,7 @@ class LocationCameraControllerTest { ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.eq(newCameraPosition(builder.build())), ArgumentMatchers.eq(LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS.toInt()), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any(CancelableCallback::class.java) ) } @@ -1716,6 +1746,7 @@ class LocationCameraControllerTest { ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.any(CameraUpdate::class.java), ArgumentMatchers.eq(LocationComponentConstants.TRANSITION_ANIMATION_DURATION_MS.toInt()), + ArgumentMatchers.any(Boolean::class.java), callbackCaptor.capture() ) val latLng = LatLng(10.0, 10.0) @@ -1740,6 +1771,7 @@ class LocationCameraControllerTest { ArgumentMatchers.any( CameraUpdate::class.java ), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) callbackCaptor.value.onFinish() @@ -1764,6 +1796,7 @@ class LocationCameraControllerTest { ArgumentMatchers.any( CameraUpdate::class.java ), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.nullable(CancelableCallback::class.java) ) } @@ -1809,6 +1842,7 @@ class LocationCameraControllerTest { ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.eq(cameraUpdate), ArgumentMatchers.eq(1200), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any( CancelableCallback::class.java ) @@ -1857,6 +1891,7 @@ class LocationCameraControllerTest { ArgumentMatchers.eq(maplibreMap), ArgumentMatchers.eq(cameraUpdate), ArgumentMatchers.eq(1200), + ArgumentMatchers.any(Boolean::class.java), ArgumentMatchers.any( CancelableCallback::class.java ) diff --git a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapLibreMapTest.kt b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapLibreMapTest.kt index a84cb1542072..c3254f693869 100644 --- a/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapLibreMapTest.kt +++ b/platform/android/MapLibreAndroid/src/test/java/org/maplibre/android/maps/MapLibreMapTest.kt @@ -76,7 +76,7 @@ class MapLibreMapTest { val expected = CameraPosition.Builder().target(target).build() val update = CameraUpdateFactory.newCameraPosition(expected) maplibreMap.moveCamera(update, callback) - verify { transform.moveCamera(maplibreMap, update, callback) } + verify { transform.moveCamera(maplibreMap, update, true, callback) } verify { developerAnimationListener.onDeveloperAnimationStarted() } } @@ -87,7 +87,7 @@ class MapLibreMapTest { val expected = CameraPosition.Builder().target(target).build() val update = CameraUpdateFactory.newCameraPosition(expected) maplibreMap.easeCamera(update, callback) - verify { transform.easeCamera(maplibreMap, update, MapLibreConstants.ANIMATION_DURATION, true, callback) } + verify { transform.easeCamera(maplibreMap, update, MapLibreConstants.ANIMATION_DURATION, true, true, callback) } verify { developerAnimationListener.onDeveloperAnimationStarted() } } @@ -98,7 +98,7 @@ class MapLibreMapTest { val expected = CameraPosition.Builder().target(target).build() val update = CameraUpdateFactory.newCameraPosition(expected) maplibreMap.animateCamera(update, callback) - verify { transform.animateCamera(maplibreMap, update, MapLibreConstants.ANIMATION_DURATION, callback) } + verify { transform.animateCamera(maplibreMap, update, MapLibreConstants.ANIMATION_DURATION, true, callback) } verify { developerAnimationListener.onDeveloperAnimationStarted() } } diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt index 8afad9bf94be..1717ee451d08 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/maplayout/SimpleMapActivity.kt @@ -6,6 +6,7 @@ import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import org.maplibre.android.maps.* import org.maplibre.android.testapp.R +import org.maplibre.android.testapp.activity.plugin.PluginProtocolExample import org.maplibre.android.testapp.utils.ApiKeyUtils import org.maplibre.android.testapp.utils.NavUtils @@ -27,6 +28,8 @@ class SimpleMapActivity : AppCompatActivity() { mapView = findViewById(R.id.mapView) mapView.onCreate(savedInstanceState) mapView.getMapAsync { + val protocolExample = PluginProtocolExample(); + mapView.addPluginProtocolHandler(protocolExample); val key = ApiKeyUtils.getApiKey(applicationContext) if (key == null || key == "YOUR_API_KEY_GOES_HERE") { it.setStyle( diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/plugin/PluginProtocolExample.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/plugin/PluginProtocolExample.kt new file mode 100644 index 000000000000..f027dde05fc3 --- /dev/null +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/plugin/PluginProtocolExample.kt @@ -0,0 +1,104 @@ +package org.maplibre.android.testapp.activity.plugin + +import org.maplibre.android.plugin.PluginProtocolHandler +import org.maplibre.android.plugin.PluginProtocolHandlerResource +import org.maplibre.android.plugin.PluginProtocolHandlerResponse +import java.net.URI + + +class PluginProtocolExample : PluginProtocolHandler() { + var styleLoaded: Boolean = false; + + override fun canRequestResource(resource: PluginProtocolHandlerResource?): Boolean { + if (!styleLoaded) { + styleLoaded = true; + return true; + } + return false; + + } + + + override fun requestResource(resource: PluginProtocolHandlerResource?): PluginProtocolHandlerResponse? { + // Return some data here + var tempResult = PluginProtocolHandlerResponse(); + + val tempStyle: String = + "{\n" + + " \"id\": \"43f36e14-e3f5-43c1-84c0-50a9c80dc5c7\",\n" + + " \"name\": \"MapLibre\",\n" + + " \"zoom\": 0.8619833357855968,\n" + + " \"pitch\": 0,\n" + + " \"center\": [\n" + + " 17.65431710431244,\n" + + " 32.954120326746775\n" + + " ],\n" + + " \"layers\": [\n" + + " {\n" + + " \"id\": \"background\",\n" + + " \"type\": \"background\",\n" + + " \"paint\": {\n" + + " \"background-color\": \"#000000\"\n" + + " },\n" + + " \"filter\": [\n" + + " \"all\"\n" + + " ],\n" + + " \"layout\": {\n" + + " \"visibility\": \"visible\"\n" + + " },\n" + + " \"maxzoom\": 24\n" + + " },\n"+ + " {\n" + + " \"id\": \"coastline\",\n" + + " \"type\": \"line\",\n" + + " \"paint\": {\n" + + " \"line-blur\": 0.5,\n" + + " \"line-color\": \"#198EC8\",\n" + + " \"line-width\": {\n" + + " \"stops\": [\n" + + " [\n" + + " 0,\n" + + " 2\n" + + " ],\n" + + " [\n" + + " 6,\n" + + " 6\n" + + " ],\n" + + " [\n" + + " 14,\n" + + " 9\n" + + " ],\n" + + " [\n" + + " 22,\n" + + " 18\n" + + " ]\n" + + " ]\n" + + " }\n" + + " },\n" + + " \"filter\": [\n" + + " \"all\"\n" + + " ],\n" + + " \"layout\": {\n" + + " \"line-cap\": \"round\",\n" + + " \"line-join\": \"round\",\n" + + " \"visibility\": \"visible\"\n" + + " },\n" + + " \"source\": \"maplibre\",\n" + + " \"maxzoom\": 24,\n" + + " \"minzoom\": 0,\n" + + " \"source-layer\": \"countries\"\n" + + " }"+ + " ],\n"+ + " \"sources\": {\n" + + " \"maplibre\": {\n" + + " \"url\": \"https://demotiles.maplibre.org/tiles/tiles.json\",\n" + + " \"type\": \"vector\"\n" + + " },"+ + " \"version\": 8\n"+ + " } }\n"; + + tempResult.generateBuffer(tempStyle); + return tempResult; + } + +} diff --git a/platform/android/VERSION b/platform/android/VERSION index b700dc1d471e..f36e00abc867 100644 --- a/platform/android/VERSION +++ b/platform/android/VERSION @@ -1 +1 @@ -12.0.1 +12.0.2 diff --git a/platform/android/android.cmake b/platform/android/android.cmake index cb9e28e5a5de..72fc11d7276f 100644 --- a/platform/android/android.cmake +++ b/platform/android/android.cmake @@ -45,6 +45,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/android/src/string_util.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/thread.cpp ${PROJECT_SOURCE_DIR}/platform/android/src/timer.cpp + ${PROJECT_SOURCE_DIR}/platform/android/MapLibreAndroid/src/cpp/plugin/plugin_file_source.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_backend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/gfx/headless_frontend.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/map/map_snapshotter.cpp @@ -61,6 +62,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_database.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_download.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/online_file_source.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/plugin_file_source.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/$,pmtiles_file_source.cpp,pmtiles_file_source_stub.cpp> ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/sqlite3.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/bidi.cpp diff --git a/platform/android/build.gradle.kts b/platform/android/build.gradle.kts index a992c50ae1bd..5253b262bb22 100644 --- a/platform/android/build.gradle.kts +++ b/platform/android/build.gradle.kts @@ -8,14 +8,14 @@ plugins { } -nexusPublishing { - repositories { - sonatype { - stagingProfileId.set(extra["sonatypeStagingProfileId"] as String?) - username.set(extra["mavenCentralUsername"] as String?) - password.set(extra["mavenCentralPassword"] as String?) - nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) - snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) - } - } -} +// nexusPublishing { +// repositories { +// sonatype { +// stagingProfileId.set(extra["sonatypeStagingProfileId"] as String?) +// username.set(extra["mavenCentralUsername"] as String?) +// password.set(extra["mavenCentralPassword"] as String?) +// nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) +// snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) +// } +// } +// } diff --git a/platform/android/buildSrc/src/main/kotlin/maplibre.gradle-publish.gradle.kts b/platform/android/buildSrc/src/main/kotlin/maplibre.gradle-publish.gradle.kts index 7d0b157f5029..5c9375f98345 100644 --- a/platform/android/buildSrc/src/main/kotlin/maplibre.gradle-publish.gradle.kts +++ b/platform/android/buildSrc/src/main/kotlin/maplibre.gradle-publish.gradle.kts @@ -44,72 +44,68 @@ project.logger.lifecycle(project.extra["versionName"].toString()) version = project.extra["versionName"] as String group = project.extra["mapLibreArtifactGroupId"] as String -fun configureMavenPublication( +fun PublishingExtension.configureMavenPublication( renderer: String, publicationName: String, artifactIdPostfix: String, descriptionPostfix: String, buildType: String = "Release" ) { - publishing { - publications { - create(publicationName) { - groupId = project.group.toString() - artifactId = "${project.extra["mapLibreArtifactId"]}$artifactIdPostfix" - version = project.version.toString() - - from(components["${renderer}${buildType}"]) - - pom { - name.set("${project.extra["mapLibreArtifactTitle"]}$descriptionPostfix") - description.set("${project.extra["mapLibreArtifactTitle"]}$descriptionPostfix") - url.set(project.extra["mapLibreArtifactUrl"].toString()) - licenses { - license { - name.set(project.extra["mapLibreArtifactLicenseName"].toString()) - url.set(project.extra["mapLibreArtifactLicenseUrl"].toString()) - } - } - developers { - developer { - id.set(project.extra["mapLibreDeveloperId"].toString()) - name.set(project.extra["mapLibreDeveloperName"].toString()) - email.set("team@maplibre.org") - } + publications { + create(publicationName) { + groupId = project.group.toString().replace("org.maplibre.gl", "org.hudhud.maplibre.gl") + artifactId = "${project.extra["mapLibreArtifactId"]}$artifactIdPostfix" + version = project.version.toString() + + from(components["${renderer}${buildType}"]) + + pom { + name.set("${project.extra["mapLibreArtifactTitle"]}$descriptionPostfix") + description.set("${project.extra["mapLibreArtifactTitle"]}$descriptionPostfix") + url.set(project.extra["mapLibreArtifactUrl"].toString()) + licenses { + license { + name.set(project.extra["mapLibreArtifactLicenseName"].toString()) + url.set(project.extra["mapLibreArtifactLicenseUrl"].toString()) } - scm { - connection.set(project.extra["mapLibreArtifactScmUrl"].toString()) - developerConnection.set(project.extra["mapLibreArtifactScmUrl"].toString()) - url.set(project.extra["mapLibreArtifactUrl"].toString()) + } + developers { + developer { + id.set(project.extra["mapLibreDeveloperId"].toString()) + name.set(project.extra["mapLibreDeveloperName"].toString()) + email.set("team@maplibre.org") } } + scm { + connection.set(project.extra["mapLibreArtifactScmUrl"].toString()) + developerConnection.set(project.extra["mapLibreArtifactScmUrl"].toString()) + url.set(project.extra["mapLibreArtifactUrl"].toString()) + } } } } } - -// workaround for https://github.com/gradle/gradle/issues/26091#issuecomment-1836156762 -// https://github.com/gradle-nexus/publish-plugin/issues/208 -tasks { - withType { - dependsOn(withType()) - } -} - afterEvaluate { - configureMavenPublication("opengl", "defaultrelease", "", "") - configureMavenPublication("opengl", "defaultdebug", "-debug", " (Debug)", "Debug") - configureMavenPublication("vulkan", "vulkanrelease", "-vulkan", "(Vulkan)") - configureMavenPublication("vulkan", "vulkandebug", "-vulkan-debug", "(Vulkan, Debug)", "Debug") - // Right now this is the same as the first, but in the future we might release a major version - // which defaults to Vulkan (or has support for multiple backends). We will keep using only - // OpenGL ES with this artifact ID if that happens. - configureMavenPublication("opengl", "openglrelease", "-opengl", " (OpenGL ES)") - configureMavenPublication("opengl", "opengldebug", "-opengl-debug", " (OpenGL ES, Debug)", "Debug") + publishing { + configureMavenPublication("opengl", "defaultrelease", "", "") + configureMavenPublication("opengl", "defaultdebug", "-debug", " (Debug)", "Debug") + configureMavenPublication("vulkan", "vulkanrelease", "-vulkan", "(Vulkan)") + configureMavenPublication("vulkan", "vulkandebug", "-vulkan-debug", "(Vulkan, Debug)", "Debug") + + repositories { + maven { + name = "GithubPackages" + url = uri("https://maven.pkg.github.com/HudHud-Maps/maplibre-native") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + } } - afterEvaluate { android.libraryVariants.forEach { variant -> tasks.named("androidJavadocs", Javadoc::class.java).configure { @@ -120,6 +116,6 @@ afterEvaluate { } } -signing { - sign(publishing.publications) -} +// signing { +// sign(publishing.publications) +// } diff --git a/platform/darwin/BUILD.bazel b/platform/darwin/BUILD.bazel index 7680a08812f0..cd9f67ca4912 100644 --- a/platform/darwin/BUILD.bazel +++ b/platform/darwin/BUILD.bazel @@ -308,8 +308,12 @@ exports_files( "app/PluginLayerTestStyle.json", "app/PluginLayerExample.h", "app/PluginLayerExample.mm", + "app/StyleFilterExample.h", + "app/StyleFilterExample.mm", "app/PluginLayerExampleMetalRendering.h", "app/PluginLayerExampleMetalRendering.mm", + "app/PluginProtocolExample.h", + "app/PluginProtocolExample.mm", "test/amsterdam.geojson", "test/MLNSDKTestHelpers.swift", "app/CustomStyleLayerExample.h", diff --git a/platform/darwin/app/PluginLayerExample.mm b/platform/darwin/app/PluginLayerExample.mm index 0cb01054d155..1339f51289dd 100644 --- a/platform/darwin/app/PluginLayerExample.mm +++ b/platform/darwin/app/PluginLayerExample.mm @@ -1,5 +1,14 @@ #import "PluginLayerExample.h" +@interface PluginLayerExample () { + +} + +@property BOOL logFeatures; + +@end + + @implementation PluginLayerExample @@ -7,15 +16,24 @@ @implementation PluginLayerExample +(MLNPluginLayerCapabilities *)layerCapabilities { MLNPluginLayerCapabilities *tempResult = [[MLNPluginLayerCapabilities alloc] init]; - tempResult.layerID = @"plugin-layer-test"; + tempResult.layerID = @"maplibre::filter_features"; tempResult.requiresPass3D = YES; + tempResult.supportsReadingTileFeatures = YES; return tempResult; } +-(id)init { + if (self = [super init]) { + self.logFeatures = NO; + } + return self; +} + // The overrides --(void)onRenderLayer { - NSLog(@"PluginLayerExample: On Render Layer"); +-(void)onRenderLayer:(MLNMapView *)mapView + renderEncoder:(id)renderEncoder { + //NSLog(@"PluginLayerExample: On Render Layer"); } @@ -24,7 +42,48 @@ -(void)onUpdateLayer { } -(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { - // NSLog(@"Layer Properties: %@", layerProperties); + // NSLog(@"Layer Properties: %@", layerProperties); +} + +-(void)featureLoaded:(MLNPluginLayerTileFeature *)tileFeature { + + // Writing a single string since the tile loading is multithreaded and the output can get interwoven + NSMutableString *outputStr = [NSMutableString string]; + [outputStr appendFormat:@"Tile Feature (id:%@) Properties: %@\n", tileFeature.featureID, tileFeature.featureProperties]; + + for (NSValue *v in tileFeature.featureCoordinates) { + + CLLocationCoordinate2D coord; + [v getValue:&coord]; + + [outputStr appendFormat:@" -> (%f, %f) \n", coord.latitude, coord.longitude]; + + } + + NSLog(@"Feature: %@", outputStr); +} + +-(void)featureUnloaded:(MLNPluginLayerTileFeature *)tileFeature { + // NSLog(@"Tile Features Unloaded: %@", tileFeature.featureProperties); + +} + + +/// Called when a set of features are loaded from the tile +- (void)onFeatureCollectionLoaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + //NSLog(@"Feature Collection Loaded for tile: %@", tileFeatureCollection.tileID); + for (MLNPluginLayerTileFeature *feature in tileFeatureCollection.features) { + [self featureLoaded:feature]; + } + +} + +/// Called when a set of features are unloaded because the tile goes out of scene/etc +- (void)onFeatureCollectionUnloaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + //NSLog(@"Feature Collection Unloaded for tile: %@", tileFeatureCollection.tileID); + for (MLNPluginLayerTileFeature *feature in tileFeatureCollection.features) { + [self featureUnloaded:feature]; + } } @end diff --git a/platform/darwin/app/PluginLayerTestStyle.json b/platform/darwin/app/PluginLayerTestStyle.json index 25ce408bd77f..b650177ef0f7 100644 --- a/platform/darwin/app/PluginLayerTestStyle.json +++ b/platform/darwin/app/PluginLayerTestStyle.json @@ -583,6 +583,13 @@ ] } }, + { "id": "centroid-features", + "type": "maplibre::filter_features", + "source": "maplibre", + "source-layer": "countries", + "maxzoom": 24, + "minzoom": 1 + }, { "id": "crimea-fill", "type": "fill", diff --git a/platform/darwin/app/PluginProtocolExample.h b/platform/darwin/app/PluginProtocolExample.h new file mode 100644 index 000000000000..63cf0fee0d99 --- /dev/null +++ b/platform/darwin/app/PluginProtocolExample.h @@ -0,0 +1,9 @@ +#import "MLNPluginProtocolHandler.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PluginProtocolExample : MLNPluginProtocolHandler + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/app/PluginProtocolExample.mm b/platform/darwin/app/PluginProtocolExample.mm new file mode 100644 index 000000000000..8915f9b514a6 --- /dev/null +++ b/platform/darwin/app/PluginProtocolExample.mm @@ -0,0 +1,23 @@ +#import "PluginProtocolExample.h" + +@implementation PluginProtocolExample + +-(BOOL)canRequestResource:(MLNPluginProtocolHandlerResource *)resource { + if ([resource.resourceURL containsString:@"pluginProtocol"]) { + return YES; + } + return NO; +} + +-(MLNPluginProtocolHandlerResponse *)requestResource:(MLNPluginProtocolHandlerResource *)resource { + + NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"PluginLayerTestStyle.json" ofType:nil]]; + + MLNPluginProtocolHandlerResponse *response = [[MLNPluginProtocolHandlerResponse alloc] init]; + response.data = data; + return response; + +} + + +@end diff --git a/platform/darwin/app/StyleFilterExample.h b/platform/darwin/app/StyleFilterExample.h new file mode 100644 index 000000000000..ea694e6d4c3e --- /dev/null +++ b/platform/darwin/app/StyleFilterExample.h @@ -0,0 +1,9 @@ +#import "MLNStyleFilter.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface StyleFilterExample : MLNStyleFilter + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/app/StyleFilterExample.mm b/platform/darwin/app/StyleFilterExample.mm new file mode 100644 index 000000000000..730e36b75f3d --- /dev/null +++ b/platform/darwin/app/StyleFilterExample.mm @@ -0,0 +1,60 @@ +#import "StyleFilterExample.h" + +@implementation StyleFilterExample + +// This will filter the data passed in +-(NSData *)filterData:(NSData *)data { + // Don't call super + + // This example will remove any layer that has "metal-rendering-layer" in the id + + // Parse the JSON: Make the containers mutable + NSError *error = nil; + NSMutableDictionary *styleDictionary = [NSJSONSerialization JSONObjectWithData:data + options:NSJSONReadingMutableContainers + error:&error]; + + NSData *tempResult = data; + if (styleDictionary) { + + // Get the layer array + NSMutableArray *layerArray = [styleDictionary objectForKey:@"layers"]; + + // Create an array to hold which objects to remove since we can't remove them in the loop + NSMutableArray *removedLayers = [NSMutableArray array]; + + // Loop the layers and look for any layers that have the search string in them + for (NSMutableDictionary *layer in layerArray) { + NSString *layerID = [layer objectForKey:@"id"]; + + // If we find the layers we're looking for, add them to the list of layers to remove + if ([layerID containsString:@"metal-rendering-layer"]) { + [removedLayers addObject:layer]; + } + } + + // Go through and remove any layers that were found + for (NSMutableDictionary *l in removedLayers) { + [layerArray removeObject:l]; + } + + // Re-create the JSON, this time the layers we filtered out won't be there + NSData *filteredStyleJSON = [NSJSONSerialization dataWithJSONObject:styleDictionary + options:0 + error:&error]; + + // If the JSON write is successful, then set the output to the new json style + if (filteredStyleJSON) { + tempResult = filteredStyleJSON; + } + + } + + // Return the data + return tempResult; + +} + + + +@end diff --git a/platform/darwin/bazel/files.bzl b/platform/darwin/bazel/files.bzl index 872b0352b38d..dfeea91ea135 100644 --- a/platform/darwin/bazel/files.bzl +++ b/platform/darwin/bazel/files.bzl @@ -9,6 +9,7 @@ MLN_GENERATED_DARWIN_STYLE_SOURCE = [ "src/MLNLineStyleLayer.mm", "src/MLNRasterStyleLayer.mm", "src/MLNSymbolStyleLayer.mm", + "src/MLNLocationIndicatorStyleLayer.mm", ] MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS = [ @@ -22,6 +23,7 @@ MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS = [ "src/MLNFillStyleLayer.h", "src/MLNHillshadeStyleLayer.h", "src/MLNRasterStyleLayer.h", + "src/MLNLocationIndicatorStyleLayer.h", ] MLN_GENERATED_DARWIN_STYLE_HEADERS = [ @@ -34,6 +36,7 @@ MLN_GENERATED_DARWIN_STYLE_HEADERS = [ "src/MLNCircleStyleLayer_Private.h", "src/MLNFillStyleLayer_Private.h", "src/MLNHillshadeStyleLayer_Private.h", + "src/MLNLocationIndicatorStyleLayer_Private.h", ] + MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS MLN_GENERATED_DARWIN_TEST_CODE = [ @@ -47,6 +50,7 @@ MLN_GENERATED_DARWIN_TEST_CODE = [ "test/MLNHillshadeStyleLayerTests.mm", "test/MLNLineStyleLayerTests.mm", "test/MLNSymbolStyleLayerTests.mm", + "test/MLNLocationIndicatorStyleLayerTests.mm", ] MLN_DARWIN_OBJC_HEADERS = [ @@ -109,6 +113,9 @@ MLN_DARWIN_OBJC_HEADERS = [ "src/NSValue+MLNAdditions.h", "src/MLNPluginLayer.h", "src/MLNPluginStyleLayer.h", + "src/MLNPluginProtocolHandler.h", + "src/MLNStyleFilter.h", + "src/MLNNetworkResponse.h", ] MLN_DARWIN_OBJCPP_HEADERS = [ @@ -167,6 +174,7 @@ MLN_DARWIN_PRIVATE_HEADERS = [ "src/NSExpression+MLNPrivateAdditions.h", "src/NSPredicate+MLNPrivateAdditions.h", "src/MLNPluginStyleLayer_Private.h", + "src/MLNStyleFilter_Private.h", ] MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ @@ -224,6 +232,9 @@ MLN_DARWIN_PUBLIC_OBJCPP_SOURCE = [ "src/NSValue+MLNStyleAttributeAdditions.mm", "src/MLNPluginLayer.mm", "src/MLNPluginStyleLayer.mm", + "src/MLNPluginProtocolHandler.mm", + "src/MLNStyleFilter.mm", + "src/MLNNetworkResponse.mm", ] MLN_DARWIN_PUBLIC_OBJCPP_CUSTOM_DRAWABLE_SOURCE = [ "src/MLNCustomDrawableStyleLayer_Private.h", diff --git a/platform/darwin/core/http_file_source.mm b/platform/darwin/core/http_file_source.mm index 62ced8e7d1f3..1964968dc3b7 100644 --- a/platform/darwin/core/http_file_source.mm +++ b/platform/darwin/core/http_file_source.mm @@ -12,7 +12,6 @@ #import #include - #include #include @@ -292,11 +291,24 @@ BOOL isValidMapboxEndpoint(NSURL *url) { assert(session); + if ([networkManager.delegate respondsToSelector:@selector(willSendRequest:)]) { + req = [networkManager.delegate willSendRequest:req]; + } + request->task = [session dataTaskWithRequest:req completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) { session = nil; + if ([networkManager.delegate respondsToSelector:@selector(didReceiveResponse:)]) { + MLNInternalNetworkResponse *networkResponse = [MLNInternalNetworkResponse responseWithData:data urlResponse:res error:error]; + networkResponse = [networkManager.delegate didReceiveResponse:networkResponse]; + data = networkResponse.data; + res = networkResponse.response; + error = networkResponse.error; + } + + if (error && [error code] == NSURLErrorCancelled) { [MLNNativeNetworkManager.sharedManager cancelDownloadEventForResponse:res]; return; diff --git a/platform/darwin/core/native_apple_interface.m b/platform/darwin/core/native_apple_interface.m index edd3b981d76b..3730d5cafbcf 100644 --- a/platform/darwin/core/native_apple_interface.m +++ b/platform/darwin/core/native_apple_interface.m @@ -1,6 +1,23 @@ #import #import +@implementation MLNInternalNetworkResponse + ++(MLNInternalNetworkResponse *)responseWithData:(NSData *)data + urlResponse:(NSURLResponse *)response + error:(NSError *)error { + + MLNInternalNetworkResponse *tempResult = [[MLNInternalNetworkResponse alloc] init]; + tempResult.data = data; + tempResult.response = response; + tempResult.error = error; + return tempResult; +} + +@end + + + @implementation MLNNativeNetworkManager static MLNNativeNetworkManager *instance = nil; diff --git a/platform/darwin/darwin.cmake b/platform/darwin/darwin.cmake index 1bdab18ae771..f9c64f1a8f39 100644 --- a/platform/darwin/darwin.cmake +++ b/platform/darwin/darwin.cmake @@ -75,6 +75,7 @@ target_sources( ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_database.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/offline_download.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/online_file_source.cpp + ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/plugin_file_source.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/$,pmtiles_file_source.cpp,pmtiles_file_source_stub.cpp> ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/storage/sqlite3.cpp ${PROJECT_SOURCE_DIR}/platform/default/src/mbgl/text/bidi.cpp @@ -123,6 +124,7 @@ set(MLN_GENERATED_DARWIN_STYLE_SOURCE "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNLineStyleLayer.mm" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNRasterStyleLayer.mm" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNSymbolStyleLayer.mm" + "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNLocationIndicatorStyleLayer.mm" ) set(MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS @@ -136,6 +138,7 @@ set(MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNFillStyleLayer.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNHillshadeStyleLayer.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNRasterStyleLayer.h" + "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNLocationIndicatorStyleLayer.h" ) set(MLN_GENERATED_DARWIN_STYLE_HEADERS @@ -148,6 +151,7 @@ set(MLN_GENERATED_DARWIN_STYLE_HEADERS "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNCircleStyleLayer_Private.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNFillStyleLayer_Private.h" "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNHillshadeStyleLayer_Private.h" + "${MLN_GENERATED_DARWIN_CODE_DIR}/MLNLocationIndicatorStyleLayer_Private.h" ${MLN_GENERATED_DARWIN_STYLE_PUBLIC_HEADERS} ) @@ -176,6 +180,8 @@ add_library( "${CMAKE_CURRENT_LIST_DIR}/app/CustomStyleLayerExample.m" "${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExample.mm" "${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExampleMetalRendering.mm" + "${CMAKE_CURRENT_LIST_DIR}/app/PluginProtocolExample.mm" + "${CMAKE_CURRENT_LIST_DIR}/app/StyleFilterExample.mm" ) target_link_libraries( diff --git a/platform/darwin/include/mbgl/interface/native_apple_interface.h b/platform/darwin/include/mbgl/interface/native_apple_interface.h index 7633819bb302..1118da1e3f67 100644 --- a/platform/darwin/include/mbgl/interface/native_apple_interface.h +++ b/platform/darwin/include/mbgl/interface/native_apple_interface.h @@ -4,12 +4,28 @@ NS_ASSUME_NONNULL_BEGIN @class MLNNativeNetworkManager; +@interface MLNInternalNetworkResponse : NSObject + +@property (retain, nullable) NSError *error; +@property (retain, nullable) NSData *data; +@property (retain, nullable) NSURLResponse *response; + ++ (MLNInternalNetworkResponse *)responseWithData:(NSData *)data + urlResponse:(NSURLResponse *)response + error:(NSError *)error; + +@end + @protocol MLNNativeNetworkDelegate @optional - (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkManager; +- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request; + +- (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response; + @required - (NSURLSessionConfiguration *)sessionConfiguration; diff --git a/platform/darwin/scripts/generate-style-code.mjs b/platform/darwin/scripts/generate-style-code.mjs index 82c6bc4e50e0..c0da9e47e876 100644 --- a/platform/darwin/scripts/generate-style-code.mjs +++ b/platform/darwin/scripts/generate-style-code.mjs @@ -8,9 +8,6 @@ import { readAndCompile, writeIfModified, camelize, unhyphenate } from "../../.. import styleSpec from "../../../scripts/style-spec.mjs"; -delete styleSpec.layer.type.values["location-indicator"]; -delete styleSpec["layout_location-indicator"] -delete styleSpec["paint_location-indicator"]; import cocoaConventions from './style-spec-cocoa-conventions-v8.json' with { type: "json" }; import styleSpecOverrides from './style-spec-overrides-v8.json' with { type: "json" }; @@ -232,6 +229,8 @@ global.objCTestValue = function (property, layerType, arraysAsStructs, indent) { return `@"{'top','bottom'}"`; case 'mode': return `@"{'horizontal','vertical'}"`; + case 'location': + return '@"{1, 2, 3}"'; default: throw new Error(`unknown array type for ${property.name}`); } @@ -288,6 +287,8 @@ global.mbglTestValue = function (property, layerType) { return '{ mbgl::style::SymbolAnchorType::Top, mbgl::style::SymbolAnchorType::Bottom }'; case 'mode': return '{ mbgl::style::TextWritingModeType::Horizontal, mbgl::style::TextWritingModeType::Vertical }'; + case 'location': + return '{1, 2, 3}'; default: throw new Error(`unknown array type for ${property.name}`); } @@ -702,6 +703,7 @@ global.propertyType = function (property) { case 'position': case 'offset': case 'translate': + case 'location': return 'NSValue *'; case 'anchor': case 'mode': @@ -731,6 +733,11 @@ global.valueTransformerArguments = function (property) { case 'boolean': return ['bool', objCType]; case 'number': + if (/bearing$/.test(property.name) && + property.period == 360 && + property.units =='degrees') { + return ['mbgl::style::Rotation', objCType]; + } return ['float', objCType]; case 'formatted': return ['mbgl::style::expression::Formatted', objCType]; @@ -763,6 +770,8 @@ global.valueTransformerArguments = function (property) { return ['std::vector', objCType, 'mbgl::style::SymbolAnchorType', 'MLNTextAnchor']; case 'mode': return ['std::vector', objCType, 'mbgl::style::TextWritingModeType', 'MLNTextWritingMode']; + case 'location': + return ['std::array', objCType]; default: throw new Error(`unknown array type for ${property.name}`); } @@ -822,6 +831,8 @@ global.mbglType = function(property) { return 'std::vector'; case 'mode': return 'std::vector'; + case 'location': + return 'std::array'; default: throw new Error(`unknown array type for ${property.name}`); } diff --git a/platform/darwin/scripts/style-spec-overrides-v8.json b/platform/darwin/scripts/style-spec-overrides-v8.json index 882707b9358c..3e4f6c123896 100644 --- a/platform/darwin/scripts/style-spec-overrides-v8.json +++ b/platform/darwin/scripts/style-spec-overrides-v8.json @@ -42,6 +42,9 @@ }, "background": { "doc": "An ``MLNBackgroundStyleLayer`` is a style layer that covers the entire map. Use a background style layer to configure a color or pattern to show below all other map content. If the style’s other layers use the Mapbox Streets source, the background style layer is responsible for drawing land, whereas the oceans and other bodies of water are drawn by ``MLNBackgroundStyleLayer`` objects.\n\nA background style layer is typically the bottommost layer in a style, because it covers the entire map and can occlude any layers below it. You can therefore access it by getting the last item in the ``MLNStyle/layers`` array.\n\nIf the background style layer is transparent or omitted from the style, any portion of the map view that does not show another style layer is transparent." + }, + "location-indicator": { + "doc": "An ``MLNLocationIndicatorStyleLayer`` is a style layer that renders a visual representation of the device's current location on the map.\n\nUse a location-indicator style layer to display a puck-like indicator that shows the user’s position, heading, and accuracy radius. This layer is typically used in conjunction with the device’s location services to track and highlight the user’s movement in real time. The appearance of the indicator can be customized using style properties such as image, color, and bearing.\n\nUnlike other style layers that render geographic features from vector or raster sources, a location-indicator layer displays dynamic, device-driven data provided by the location manager. This layer does not rely on source data and is updated automatically as the device location changes." } } } diff --git a/platform/darwin/src/MLNCustomStyleLayer.h b/platform/darwin/src/MLNCustomStyleLayer.h index d0c57a714d02..fd6247958270 100644 --- a/platform/darwin/src/MLNCustomStyleLayer.h +++ b/platform/darwin/src/MLNCustomStyleLayer.h @@ -32,6 +32,7 @@ typedef struct MLNStyleLayerDrawingContext { CGFloat fieldOfView; /// A 4Γ—4 matrix representing the map view’s current projection state. MLNMatrix4 projectionMatrix; + MLNMatrix4 nearClippedProjMatrix; } MLNStyleLayerDrawingContext; /// A style layer that is rendered by Metal code that you provide. diff --git a/platform/darwin/src/MLNCustomStyleLayer.mm b/platform/darwin/src/MLNCustomStyleLayer.mm index 5e3107bb4c34..b529d0bf79c2 100644 --- a/platform/darwin/src/MLNCustomStyleLayer.mm +++ b/platform/darwin/src/MLNCustomStyleLayer.mm @@ -116,7 +116,8 @@ void render(const mbgl::style::CustomLayerRenderParameters& parameters) { .direction = mbgl::util::wrap(parameters.bearing, 0., 360.), .pitch = static_cast(parameters.pitch), .fieldOfView = static_cast(parameters.fieldOfView), - .projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix) + .projectionMatrix = MLNMatrix4Make(parameters.projectionMatrix), + .nearClippedProjMatrix = MLNMatrix4Make(parameters.nearClippedProjMatrix) }; if (layer.mapView) { diff --git a/platform/darwin/src/MLNNetworkConfiguration.h b/platform/darwin/src/MLNNetworkConfiguration.h index 01c0fee46f40..19fc298e22b4 100644 --- a/platform/darwin/src/MLNNetworkConfiguration.h +++ b/platform/darwin/src/MLNNetworkConfiguration.h @@ -1,6 +1,7 @@ #import #import "MLNFoundation.h" +#import "MLNNetworkResponse.h" NS_ASSUME_NONNULL_BEGIN @@ -24,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN are not supported at this time. */ - (NSURLSession *)sessionForNetworkConfiguration:(MLNNetworkConfiguration *)configuration; + +- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request; + +- (MLNNetworkResponse *)didReceiveResponse:(MLNNetworkResponse *)response; + @end /** diff --git a/platform/darwin/src/MLNNetworkConfiguration.mm b/platform/darwin/src/MLNNetworkConfiguration.mm index 9cc8db3acbea..04f247615386 100644 --- a/platform/darwin/src/MLNNetworkConfiguration.mm +++ b/platform/darwin/src/MLNNetworkConfiguration.mm @@ -87,6 +87,40 @@ - (NSURLSession *)sessionForNetworkManager:(MLNNativeNetworkManager *)networkMan return session; } +- (NSMutableURLRequest *)willSendRequest:(NSMutableURLRequest *)request { + + if ([self.delegate respondsToSelector:@selector(willSendRequest:)]) { + return [self.delegate willSendRequest:request]; + } + + return request; + +} + +- (MLNInternalNetworkResponse *)didReceiveResponse:(MLNInternalNetworkResponse *)response { + + if ([self.delegate respondsToSelector:@selector(didReceiveResponse:)]) { + + MLNNetworkResponse *tempResponse = [MLNNetworkResponse responseWithData:response.data + urlResponse:response.response + error:response.error]; + tempResponse = [self.delegate didReceiveResponse:tempResponse]; + if (tempResponse) { + MLNInternalNetworkResponse *internalResponse = [MLNInternalNetworkResponse responseWithData:tempResponse.data + urlResponse:tempResponse.response + error:tempResponse.error]; + return internalResponse; + } else { + return nil; + } + + } + + return response; + +} + + - (NSURLSessionConfiguration *)sessionConfiguration { NSURLSessionConfiguration *sessionConfig = nil; @synchronized (self) { diff --git a/platform/darwin/src/MLNNetworkResponse.h b/platform/darwin/src/MLNNetworkResponse.h new file mode 100644 index 000000000000..d14c429c8b1d --- /dev/null +++ b/platform/darwin/src/MLNNetworkResponse.h @@ -0,0 +1,26 @@ +// +// MLNNetworkResponse.h +// App +// +// Created by Malcolm Toon on 9/22/25. +// + +#import +#import "MLNFoundation.h" + +NS_ASSUME_NONNULL_BEGIN + +MLN_EXPORT +@interface MLNNetworkResponse : NSObject + +@property (retain, nullable) NSError *error; +@property (retain, nullable) NSData *data; +@property (retain, nullable) NSURLResponse *response; + ++ (MLNNetworkResponse *)responseWithData:(NSData *)data + urlResponse:(NSURLResponse *)response + error:(NSError *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNNetworkResponse.mm b/platform/darwin/src/MLNNetworkResponse.mm new file mode 100644 index 000000000000..4588910b9d21 --- /dev/null +++ b/platform/darwin/src/MLNNetworkResponse.mm @@ -0,0 +1,25 @@ +// +// MLNNetworkResponse.m +// App +// +// Created by Malcolm Toon on 9/22/25. +// + +#import +#import "MLNNetworkResponse.h" + +@implementation MLNNetworkResponse + ++(MLNNetworkResponse *)responseWithData:(NSData *)data + urlResponse:(NSURLResponse *)response + error:(NSError *)error { + + MLNNetworkResponse *tempResult = [[MLNNetworkResponse alloc] init]; + tempResult.data = data; + tempResult.response = response; + tempResult.error = error; + return tempResult; +} + +@end + diff --git a/platform/darwin/src/MLNPluginLayer.h b/platform/darwin/src/MLNPluginLayer.h index 2e977b8d09c7..91f3eb98304f 100644 --- a/platform/darwin/src/MLNPluginLayer.h +++ b/platform/darwin/src/MLNPluginLayer.h @@ -42,6 +42,22 @@ MLN_EXPORT @end +@interface MLNPluginLayerTileFeature : NSObject + +// This is the unique feature ID in the tile source if applicable. Can be empty +@property NSString *featureID; +@property NSDictionary *featureProperties; +@property NSArray *featureCoordinates; + +@end + +@interface MLNPluginLayerTileFeatureCollection : NSObject + +@property NSArray *features; +@property NSString *tileID; // z,x,y + +@end + typedef enum { MLNPluginLayerTileKindGeometry, MLNPluginLayerTileKindRaster, @@ -55,6 +71,9 @@ MLN_EXPORT @property (copy) NSString *layerID; @property BOOL requiresPass3D; +//! Set this to true if this layer can support reading features from the tiles +@property BOOL supportsReadingTileFeatures; + //! This is a list of layer properties that this layer supports. @property (copy) NSArray *layerProperties; @@ -82,7 +101,6 @@ typedef struct MLNPluginLayerDrawingContext { MLNMatrix4 projectionMatrix; /// A 4Γ—4 matrix representing the map view’s current near clip projection state. MLNMatrix4 nearClippedProjMatrix; - } MLNPluginLayerDrawingContext; MLN_EXPORT @@ -104,6 +122,12 @@ MLN_EXPORT /// dynamic properties are updated - (void)onUpdateLayerProperties:(NSDictionary *)layerProperties; +/// Called when a set of features are loaded from the tile +- (void)onFeatureCollectionLoaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection; + +/// Called when a set of features are unloaded because the tile goes out of scene/etc +- (void)onFeatureCollectionUnloaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection; + /// Added to a map view - (void)didMoveToMapView:(MLNMapView *)mapView; diff --git a/platform/darwin/src/MLNPluginLayer.mm b/platform/darwin/src/MLNPluginLayer.mm index cff0044d05ac..0e57affd1e7f 100644 --- a/platform/darwin/src/MLNPluginLayer.mm +++ b/platform/darwin/src/MLNPluginLayer.mm @@ -1,5 +1,13 @@ #import "MLNPluginLayer.h" +@implementation MLNPluginLayerTileFeature + +@end + +@implementation MLNPluginLayerTileFeatureCollection + +@end + @implementation MLNPluginLayerProperty +(MLNPluginLayerProperty *)propertyWithName:(NSString *)propertyName @@ -66,12 +74,25 @@ -(void)onUpdateLayerProperties:(NSDictionary *)layerProperties { // Base class does nothing } +// If the layer properties indicate that this layer has a the ability to intercept +// features, then this method will be called when a feature is loaded +- (void)onFeatureCollectionLoaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + // Base class does nothing +} + +/// Called when a set of features are unloaded because the tile goes out of scene/etc +- (void)onFeatureCollectionUnloaded:(MLNPluginLayerTileFeatureCollection *)tileFeatureCollection { + // Base class does nothing +} + + /// Added to a map view - (void)didMoveToMapView:(MLNMapView *)mapView { // Base class does nothing } + @end diff --git a/platform/darwin/src/MLNPluginProtocolHandler.h b/platform/darwin/src/MLNPluginProtocolHandler.h new file mode 100644 index 000000000000..ef88a9889b35 --- /dev/null +++ b/platform/darwin/src/MLNPluginProtocolHandler.h @@ -0,0 +1,62 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef enum { + MLNPluginProtocolHandlerResourceKindUnknown, + MLNPluginProtocolHandlerResourceKindStyle, + MLNPluginProtocolHandlerResourceKindSource, + MLNPluginProtocolHandlerResourceKindTile, + MLNPluginProtocolHandlerResourceKindGlyphs, + MLNPluginProtocolHandlerResourceKindSpriteImage, + MLNPluginProtocolHandlerResourceKindSpriteJSON, + MLNPluginProtocolHandlerResourceKindImage +} MLNPluginProtocolHandlerResourceKind; + +typedef enum { + MLNPluginProtocolHandlerResourceLoadingMethodUnknown, + MLNPluginProtocolHandlerResourceLoadingMethodCacheOnly, + MLNPluginProtocolHandlerResourceLoadingMethodNetworkOnly, + MLNPluginProtocolHandlerResourceLoadingMethodAll +} MLNPluginProtocolHandlerResourceLoadingMethod; + +// TODO: Might make sense to add this to it's own file +@interface MLNTileData : NSObject + +// Optional Tile Data +@property NSString *tileURLTemplate; +@property int tilePixelRatio; +@property int tileX; +@property int tileY; +@property int tileZoom; + +@end + +@interface MLNPluginProtocolHandlerResource : NSObject + +@property MLNPluginProtocolHandlerResourceKind resourceKind; + +@property MLNPluginProtocolHandlerResourceLoadingMethod loadingMethod; + +@property NSString *resourceURL; + +// This is optional +@property MLNTileData *__nullable tileData; + +@end + +@interface MLNPluginProtocolHandlerResponse : NSObject + +@property NSData *data; + +@end + +@interface MLNPluginProtocolHandler : NSObject + +- (BOOL)canRequestResource:(MLNPluginProtocolHandlerResource *)resource; + +- (MLNPluginProtocolHandlerResponse *)requestResource:(MLNPluginProtocolHandlerResource *)resource; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNPluginProtocolHandler.mm b/platform/darwin/src/MLNPluginProtocolHandler.mm new file mode 100644 index 000000000000..a68409d104dc --- /dev/null +++ b/platform/darwin/src/MLNPluginProtocolHandler.mm @@ -0,0 +1,26 @@ +#import "MLNPluginProtocolHandler.h" + +@implementation MLNPluginProtocolHandler + +-(BOOL)canRequestResource:(MLNPluginProtocolHandlerResource *)resource { + // Base class returns false + return NO; +} + +-(MLNPluginProtocolHandlerResponse *)requestResource:(MLNPluginProtocolHandlerResource *)resource { + // Base class does nothing + return nil; +} + + +@end + +@implementation MLNPluginProtocolHandlerResource +@end + + +@implementation MLNPluginProtocolHandlerResponse +@end + +@implementation MLNTileData +@end diff --git a/platform/darwin/src/MLNPluginStyleLayer.h b/platform/darwin/src/MLNPluginStyleLayer.h index eaf7d86521b3..92bb2ef5cd0e 100644 --- a/platform/darwin/src/MLNPluginStyleLayer.h +++ b/platform/darwin/src/MLNPluginStyleLayer.h @@ -4,10 +4,17 @@ NS_ASSUME_NONNULL_BEGIN @class MLNPluginLayer; +MLN_EXPORT @interface MLNPluginStyleLayer : MLNStyleLayer - (MLNPluginLayer *)pluginLayer; +- (instancetype)initWithType:(NSString *)layerType + layerIdentifier:(NSString *)identifier + layerPropeties:(NSDictionary *)layerPropeties; + +- (void)setPluginProperty:(NSString *)propertyName value:(id)propertyValue; + @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNPluginStyleLayer.mm b/platform/darwin/src/MLNPluginStyleLayer.mm index c8dc037789c3..3c202e0d2517 100644 --- a/platform/darwin/src/MLNPluginStyleLayer.mm +++ b/platform/darwin/src/MLNPluginStyleLayer.mm @@ -1,11 +1,51 @@ #import "MLNPluginStyleLayer.h" #import "MLNPluginStyleLayer_Private.h" -#import -#import +#import "MLNStyleLayerManager.h" +#include +#include +#include +#include + #import "MLNPluginLayer.h" @implementation MLNPluginStyleLayer +- (instancetype)initWithType:(NSString *)layerType + layerIdentifier:(NSString *)identifier + layerPropeties:(NSDictionary *)layerPropeties { + + // Setup the same property paradigm that would be coming in the style + // This at a minimum creates a dictionary that has id and type + + NSMutableDictionary *layerPropertiesAggregated = [NSMutableDictionary dictionary]; + if (layerPropeties) { + [layerPropertiesAggregated addEntriesFromDictionary:layerPropeties]; + } + [layerPropertiesAggregated setObject:identifier forKey:@"id"]; + [layerPropertiesAggregated setObject:layerType forKey:@"type"]; + + mbgl::style::conversion::Error e; + auto propertiesValue = mbgl::style::makeConvertible(layerPropertiesAggregated); + + // Create the layer using the same convert method that's used by the style loading + std::optional> converted = mbgl::style::conversion::convert>(propertiesValue, e); + if (!converted) { + return nil; + } + auto layer = std::move(*converted); + + if (layer == nullptr) { + return nil; + } + + self = [super initWithPendingLayer:std::move(layer)]; + + return self; + +} + + + -(void)getStats { mbgl::style::PluginLayer *l = (mbgl::style::PluginLayer *)self.rawLayer; @@ -25,6 +65,16 @@ -(MLNPluginLayer *)pluginLayer { } +- (void)setPluginProperty:(NSString *)propertyName + value:(id)propertyValue { + + MLNPluginLayer *layer = [self pluginLayer]; + NSDictionary *updatedProperties = @{propertyName: propertyValue}; + [layer onUpdateLayerProperties:updatedProperties]; + +} + + @end diff --git a/platform/darwin/src/MLNStyle.mm b/platform/darwin/src/MLNStyle.mm index 3f4d211ff6a7..424ba5f959a5 100644 --- a/platform/darwin/src/MLNStyle.mm +++ b/platform/darwin/src/MLNStyle.mm @@ -12,6 +12,7 @@ #import "MLNHillshadeStyleLayer.h" #import "MLNRasterStyleLayer.h" #import "MLNBackgroundStyleLayer.h" +#import "MLNLocationIndicatorStyleLayer.h" #import "MLNStyleLayerManager.h" #import "MLNSource.h" diff --git a/platform/darwin/src/MLNStyleFilter.h b/platform/darwin/src/MLNStyleFilter.h new file mode 100644 index 000000000000..d9a242ed6394 --- /dev/null +++ b/platform/darwin/src/MLNStyleFilter.h @@ -0,0 +1,12 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MLNStyleFilter : NSObject + +// This will filter the data passed in +- (NSData *)filterData:(NSData *)data; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MLNStyleFilter.mm b/platform/darwin/src/MLNStyleFilter.mm new file mode 100644 index 000000000000..31c6b0f6e937 --- /dev/null +++ b/platform/darwin/src/MLNStyleFilter.mm @@ -0,0 +1,23 @@ +#import "MLNStyleFilter.h" +#include + +@interface MLNStyleFilter () { + std::shared_ptr _coreFilter; +} + +@end + +@implementation MLNStyleFilter + +-(NSData *)filterData:(NSData *)data { + // Base class does nothing but return the same data passed in + return data; +} + +// Private +-(void)setFilter:(std::shared_ptr)filter { + _coreFilter = filter; +} + + +@end diff --git a/platform/darwin/src/MLNStyleFilter_Private.h b/platform/darwin/src/MLNStyleFilter_Private.h new file mode 100644 index 000000000000..749cefcc7fd9 --- /dev/null +++ b/platform/darwin/src/MLNStyleFilter_Private.h @@ -0,0 +1,14 @@ + +#ifndef MLNStyleFilter_Private_h +#define MLNStyleFilter_Private_h + +#include +#import "MLNStyleFilter.h" + +@interface MLNStyleFilter (Private) + +- (void)setFilter:(std::shared_ptr)filter; + +@end + +#endif /* MLNStyleFilter_Private_h */ diff --git a/platform/darwin/src/MLNStyleLayer.h.ejs b/platform/darwin/src/MLNStyleLayer.h.ejs index 50d17ffa493f..a4b98e5024fb 100644 --- a/platform/darwin/src/MLNStyleLayer.h.ejs +++ b/platform/darwin/src/MLNStyleLayer.h.ejs @@ -11,7 +11,7 @@ #import "MLNFoundation.h" #import "MLN<%- -(type === 'background' ? '' : +(type === 'background' || type == 'location-indicator' ? '' : (type === 'raster' || type === 'hillshade' ? 'Foreground' : 'Vector')) %>StyleLayer.h" @@ -83,11 +83,11 @@ typedef NS_ENUM(NSUInteger, MLN<%- camelize(enumName(property)) %>) { <% } -%> MLN_EXPORT @interface MLN<%- camelize(type) %>StyleLayer : MLN<%- -(type === 'background' ? '' : +(type === 'background' || type === 'location-indicator' ? '' : (type === 'raster' || type === 'hillshade' ? 'Foreground' : 'Vector')) %>StyleLayer -<% if (type === 'background') { -%> +<% if (type === 'background' || type === 'location-indicator') { -%> /** Returns a <%- type %> style layer initialized with an identifier. diff --git a/platform/darwin/src/MLNStyleLayer.mm.ejs b/platform/darwin/src/MLNStyleLayer.mm.ejs index c706aa02ede5..c79061445523 100644 --- a/platform/darwin/src/MLNStyleLayer.mm.ejs +++ b/platform/darwin/src/MLNStyleLayer.mm.ejs @@ -58,7 +58,7 @@ namespace mbgl { @implementation MLN<%- camelize(type) %>StyleLayer -<% if (type == 'background') { -%> +<% if (type == 'background' || type == "location-indicator") { -%> - (instancetype)initWithIdentifier:(NSString *)identifier { MLNLogDebug(@"Initializing %@ with identifier: %@", NSStringFromClass([self class]), identifier); diff --git a/platform/darwin/src/MLNStyleLayerManager.mm b/platform/darwin/src/MLNStyleLayerManager.mm index 4f5e0822b1ff..1a0dd25f4e10 100644 --- a/platform/darwin/src/MLNStyleLayerManager.mm +++ b/platform/darwin/src/MLNStyleLayerManager.mm @@ -10,6 +10,7 @@ #import "MLNRasterStyleLayer_Private.h" #import "MLNSymbolStyleLayer_Private.h" #import "MLNCustomStyleLayer_Private.h" +#import "MLNLocationIndicatorStyleLayer_Private.h" #import "MLNCustomDrawableStyleLayer_Private.h" @@ -68,6 +69,11 @@ #elif !defined(MBGL_LAYER_CUSTOM_DISABLE_ALL) addLayerType(std::make_unique()); #endif +#if !defined(MBGL_LAYER_LOCATION_INDICATOR_DISABLE_ALL) + addLayerTypeCoreOnly(std::make_unique()); +#elif !defined(MBGL_LAYER_HEATMAP_DISABLE_ALL) + addLayerType(std::make_unique()); +#endif #if defined(MLN_LAYER_CUSTOM_DRAWABLE_DISABLE_RUNTIME) addLayerTypeCoreOnly(std::make_unique()); diff --git a/platform/darwin/src/MLNStyleValue_Private.h b/platform/darwin/src/MLNStyleValue_Private.h index f95638eee62a..220b8d5f2206 100644 --- a/platform/darwin/src/MLNStyleValue_Private.h +++ b/platform/darwin/src/MLNStyleValue_Private.h @@ -1,4 +1,5 @@ #import +#include #import "MLNStyleValue.h" @@ -19,6 +20,7 @@ #import #include +#include #include @@ -48,8 +50,18 @@ NS_INLINE MLNTransition MLNTransitionFromOptions(const mbgl::style::TransitionOp } NS_INLINE mbgl::style::TransitionOptions MLNOptionsFromTransition(MLNTransition transition) { + mbgl::util::UnitBezier ease = mbgl::util::DEFAULT_TRANSITION_EASE; + if (transition.ease) { + CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName:transition.ease]; + float p1[2], p2[2]; + [function getControlPointAtIndex:1 values:p1]; + [function getControlPointAtIndex:2 values:p2]; + ease = {p1[0], p1[1], p2[0], p2[1]}; + } + mbgl::style::TransitionOptions options{{MLNDurationFromTimeInterval(transition.duration)}, - {MLNDurationFromTimeInterval(transition.delay)}}; + {MLNDurationFromTimeInterval(transition.delay)}, + ease}; return options; } @@ -183,6 +195,12 @@ class MLNStyleValueTransformer { // Float void getMBGLValue(NSNumber *rawValue, float &mbglValue) { mbglValue = rawValue.floatValue; } + void getMBGLValue(NSNumber *rawValue, double &mbglValue) { mbglValue = rawValue.doubleValue; } + + void getMBGLValue(NSNumber *rawValue, mbgl::style::Rotation &mbglValue) { + mbglValue = rawValue.floatValue; + } + // String void getMBGLValue(NSString *rawValue, std::string &mbglValue) { mbglValue = rawValue.UTF8String; } @@ -216,6 +234,17 @@ class MLNStyleValueTransformer { } } + void getMBGLValue(id rawValue, std::array &mbglValue) { + if ([rawValue isKindOfClass:[NSValue class]]) { + mbglValue = [rawValue mgl_locationArrayValue]; + } else if ([rawValue isKindOfClass:[NSArray class]]) { + NSArray *array = (NSArray *)rawValue; + getMBGLValue(array[0], mbglValue[0]); + getMBGLValue(array[1], mbglValue[1]); + getMBGLValue(array[2], mbglValue[2]); + } + } + // Padding type (supports numbers and float arrays w/ sizes 1 to 4) void getMBGLValue(id rawValue, mbgl::Padding &mbglValue) { if ([rawValue isKindOfClass:[NSNumber class]]) { @@ -314,6 +343,12 @@ class MLNStyleValueTransformer { // Float static NSNumber *toMLNRawStyleValue(const float mbglStopValue) { return @(mbglStopValue); } + static NSNumber *toMLNRawStyleValue(const double mbglStopValue) { return @(mbglStopValue); } + + static NSNumber *toMLNRawStyleValue(const mbgl::style::Rotation mbglStopValue) { + return @(mbglStopValue.getAngle()); + } + // Integer static NSNumber *toMLNRawStyleValue(const int64_t mbglStopValue) { return @(mbglStopValue); } @@ -337,6 +372,10 @@ class MLNStyleValueTransformer { return [NSValue mgl_valueWithPaddingArray:mbglStopValue]; } + static NSValue *toMLNRawStyleValue(const std::array &mbglStopValue) { + return [NSValue mgl_valueWithLocationArray:mbglStopValue]; + } + // Padding type static NSValue *toMLNRawStyleValue(const mbgl::Padding &mbglStopValue) { return [NSValue mgl_valueWithPaddingArray:mbglStopValue.toArray()]; diff --git a/platform/darwin/src/MLNTypes.h b/platform/darwin/src/MLNTypes.h index 8824ad4d5fc5..f2c956e44982 100644 --- a/platform/darwin/src/MLNTypes.h +++ b/platform/darwin/src/MLNTypes.h @@ -1,5 +1,6 @@ #import #import +#import #import "MLNFoundation.h" @@ -101,6 +102,8 @@ typedef struct __attribute__((objc_boxable)) MLNTransition { The amount of time in seconds to wait before beginning the animation. */ NSTimeInterval delay; + + CAMediaTimingFunctionName ease; } MLNTransition; NS_INLINE NSString *MLNStringFromMLNTransition(MLNTransition transition) { @@ -118,10 +121,12 @@ NS_INLINE NSString *MLNStringFromMLNTransition(MLNTransition transition) { @return Returns a ``MLNTransition`` struct containing the transition attributes. */ -NS_INLINE MLNTransition MLNTransitionMake(NSTimeInterval duration, NSTimeInterval delay) { +NS_INLINE MLNTransition MLNTransitionMake(NSTimeInterval duration, NSTimeInterval delay, + CAMediaTimingFunctionName ease) { MLNTransition transition; transition.duration = duration; transition.delay = delay; + transition.ease = ease; return transition; } diff --git a/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.h b/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.h index 3d1d8e896d8d..2ea7c835ab6c 100644 --- a/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.h +++ b/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.h @@ -6,9 +6,11 @@ + (instancetype)mgl_valueWithOffsetArray:(std::array)offsetArray; + (instancetype)mgl_valueWithPaddingArray:(std::array)paddingArray; ++ (instancetype)mgl_valueWithLocationArray:(std::array)positionArray; - (std::array)mgl_offsetArrayValue; - (std::array)mgl_paddingArrayValue; +- (std::array)mgl_locationArrayValue; - (std::array)mgl_lightPositionArrayValue; @end diff --git a/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm b/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm index 5a8151102bec..ba5fba3c2fbd 100644 --- a/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm +++ b/platform/darwin/src/NSValue+MLNStyleAttributeAdditions.mm @@ -32,6 +32,12 @@ + (instancetype)mgl_valueWithPaddingArray:(std::array)paddingArray return [NSValue value:&insets withObjCType:@encode(MLNEdgeInsets)]; } ++ (instancetype)mgl_valueWithLocationArray:(std::array)locationArray +{ + CLLocation* location = [[CLLocation alloc] initWithLatitude:locationArray[0] longitude:locationArray[1]]; + return [NSValue value:&location withObjCType:@encode(CLLocation)]; +} + - (std::array)mgl_offsetArrayValue { MLNAssert(strcmp(self.objCType, @encode(CGVector)) == 0, @"Value does not represent a CGVector"); @@ -60,6 +66,18 @@ + (instancetype)mgl_valueWithPaddingArray:(std::array)paddingArray }; } +- (std::array)mgl_locationArrayValue +{ + MLNAssert(strcmp(self.objCType, @encode(CLLocation)) == 0, @"Value does not represent an CLLocation"); + CLLocation* location; + [self getValue:&location]; + return { + static_cast(location.coordinate.latitude), + static_cast(location.coordinate.longitude), + static_cast(location.altitude), + }; +} + - (std::array)mgl_lightPositionArrayValue { MLNAssert(strcmp(self.objCType, @encode(MLNSphericalPosition)) == 0, @"Value does not represent an MLNSphericalPosition"); diff --git a/platform/darwin/test/MLNLightTest.mm.ejs b/platform/darwin/test/MLNLightTest.mm.ejs index 2c481ddf6bf4..2dc120292507 100644 --- a/platform/darwin/test/MLNLightTest.mm.ejs +++ b/platform/darwin/test/MLNLightTest.mm.ejs @@ -23,8 +23,8 @@ - (void)testProperties { - MLNTransition defaultTransition = MLNTransitionMake(0, 0); - MLNTransition transition = MLNTransitionMake(6, 3); + MLNTransition defaultTransition = MLNTransitionMake(0, 0, kCAMediaTimingFunctionDefault); + MLNTransition transition = MLNTransitionMake(6, 3, kCAMediaTimingFunctionDefault); mbgl::style::TransitionOptions transitionOptions { { MLNDurationFromTimeInterval(6) }, { MLNDurationFromTimeInterval(3) } }; <% for (const property of properties) { -%> diff --git a/platform/darwin/test/MLNStyleLayerTests.mm.ejs b/platform/darwin/test/MLNStyleLayerTests.mm.ejs index 225f32128607..8c0399631e6c 100644 --- a/platform/darwin/test/MLNStyleLayerTests.mm.ejs +++ b/platform/darwin/test/MLNStyleLayerTests.mm.ejs @@ -60,7 +60,7 @@ XCTAssertEqualObjects(@(layer.rawLayer->getTypeInfo()->type), @"<%- type %>"); auto rawLayer = static_castLayer*>(layer.rawLayer); - MLNTransition transitionTest = MLNTransitionMake(5, 4); + MLNTransition transitionTest = MLNTransitionMake(5, 4, kCAMediaTimingFunctionDefault); <% for (const property of properties) { -%> <% if (property['property-type'] === 'color-ramp') continue; -%> diff --git a/platform/darwin/test/MLNStyleTests.mm b/platform/darwin/test/MLNStyleTests.mm index 9f36afeefd4b..ab9db761218a 100644 --- a/platform/darwin/test/MLNStyleTests.mm +++ b/platform/darwin/test/MLNStyleTests.mm @@ -406,7 +406,7 @@ - (void)testLanguageMatching { - (void)testTransition { - MLNTransition transitionTest = MLNTransitionMake(5, 4); + MLNTransition transitionTest = MLNTransitionMake(5, 4, kCAMediaTimingFunctionDefault); self.style.transition = transitionTest; diff --git a/platform/default/BUILD.bazel b/platform/default/BUILD.bazel index e22c12db9526..7dbb1bb097ef 100644 --- a/platform/default/BUILD.bazel +++ b/platform/default/BUILD.bazel @@ -58,6 +58,7 @@ cc_library( "src/mbgl/storage/offline_database.cpp", "src/mbgl/storage/offline_download.cpp", "src/mbgl/storage/online_file_source.cpp", + "src/mbgl/storage/plugin_file_source.cpp", "src/mbgl/storage/pmtiles_file_source.cpp", "src/mbgl/storage/sqlite3.cpp", "src/mbgl/text/bidi.cpp", diff --git a/platform/default/src/mbgl/gl/headless_backend.cpp b/platform/default/src/mbgl/gl/headless_backend.cpp index 63674eee5c37..dfb1db84233f 100644 --- a/platform/default/src/mbgl/gl/headless_backend.cpp +++ b/platform/default/src/mbgl/gl/headless_backend.cpp @@ -24,7 +24,7 @@ class HeadlessRenderableResource final : public gl::RenderableResource { void bind() override { context.bindFramebuffer = framebuffer.framebuffer; - context.scissorTest = false; + context.scissorTest = {0, 0, 0, 0}; context.viewport = {0, 0, framebuffer.size}; } diff --git a/platform/default/src/mbgl/storage/main_resource_loader.cpp b/platform/default/src/mbgl/storage/main_resource_loader.cpp index 2f523f1e8d2e..90150031adbe 100644 --- a/platform/default/src/mbgl/storage/main_resource_loader.cpp +++ b/platform/default/src/mbgl/storage/main_resource_loader.cpp @@ -65,54 +65,67 @@ class MainResourceLoaderThread { // the sources were able to request a resource. const std::size_t tasksSize = tasks.size(); - // Waterfall resource request processing and return early once resource was requested. - if (assetFileSource && assetFileSource->canRequest(resource)) { - // Asset request - tasks[req] = assetFileSource->request(resource, callback); - } else if (mbtilesFileSource && mbtilesFileSource->canRequest(resource)) { - // Local file request - tasks[req] = mbtilesFileSource->request(resource, callback); - } else if (pmtilesFileSource && pmtilesFileSource->canRequest(resource)) { - // Local file request - tasks[req] = pmtilesFileSource->request(resource, callback); - } else if (localFileSource && localFileSource->canRequest(resource)) { - // Local file request - tasks[req] = localFileSource->request(resource, callback); - } else if (databaseFileSource && databaseFileSource->canRequest(resource)) { - // Try cache only request if needed. - if (resource.loadingMethod == Resource::LoadingMethod::CacheOnly) { - tasks[req] = databaseFileSource->request(resource, callback); - } else { - // Cache request with fallback to network with cache control - tasks[req] = databaseFileSource->request(resource, [=, this](const Response& response) { - Resource res = resource; - - // Resource is in the cache - if (!response.noContent) { - if (response.isUsable()) { - callback(response); - // Set the priority of existing resource to low if it's expired but usable. - res.setPriority(Resource::Priority::Low); - } else { - // Set prior data only if it was not returned to - // the requester. Once we get 304 response from - // the network, we will forward response to the - // requester. - res.priorData = response.data; - } + // Go through custom handlers + bool requestHandledByCustomHandler = false; + auto fm = FileSourceManager::get(); + for (const auto& customFileSource : fm->getCustomFileSources()) { + if (customFileSource->canRequest(resource)) { + tasks[req] = customFileSource->request(resource, callback); + requestHandledByCustomHandler = true; + break; + } + } - // Copy response fields for cache control request - res.priorModified = response.modified; - res.priorExpires = response.expires; - res.priorEtag = response.etag; - } + if (!requestHandledByCustomHandler) { + // Waterfall resource request processing and return early once resource was requested. + if (assetFileSource && assetFileSource->canRequest(resource)) { + // Asset request + tasks[req] = assetFileSource->request(resource, callback); + } else if (mbtilesFileSource && mbtilesFileSource->canRequest(resource)) { + // Local file request + tasks[req] = mbtilesFileSource->request(resource, callback); + } else if (pmtilesFileSource && pmtilesFileSource->canRequest(resource)) { + // Local file request + tasks[req] = pmtilesFileSource->request(resource, callback); + } else if (localFileSource && localFileSource->canRequest(resource)) { + // Local file request + tasks[req] = localFileSource->request(resource, callback); + } else if (databaseFileSource && databaseFileSource->canRequest(resource)) { + // Try cache only request if needed. + if (resource.loadingMethod == Resource::LoadingMethod::CacheOnly) { + tasks[req] = databaseFileSource->request(resource, callback); + } else { + // Cache request with fallback to network with cache control + tasks[req] = databaseFileSource->request(resource, [=, this](const Response& response) { + Resource res = resource; + + // Resource is in the cache + if (!response.noContent) { + if (response.isUsable()) { + callback(response); + // Set the priority of existing resource to low if it's expired but usable. + res.setPriority(Resource::Priority::Low); + } else { + // Set prior data only if it was not returned to + // the requester. Once we get 304 response from + // the network, we will forward response to the + // requester. + res.priorData = response.data; + } + + // Copy response fields for cache control request + res.priorModified = response.modified; + res.priorExpires = response.expires; + res.priorEtag = response.etag; + } - tasks[req] = requestFromNetwork(res, std::move(tasks[req])); - }); + tasks[req] = requestFromNetwork(res, std::move(tasks[req])); + }); + } + } else if (auto networkReq = requestFromNetwork(resource, nullptr)) { + // Get from the online file source + tasks[req] = std::move(networkReq); } - } else if (auto networkReq = requestFromNetwork(resource, nullptr)) { - // Get from the online file source - tasks[req] = std::move(networkReq); } // If no new tasks were added, notify client that request cannot be processed. @@ -180,6 +193,14 @@ class MainResourceLoader::Impl { } bool canRequest(const Resource& resource) const { + // Check the custom file sources + auto fm = FileSourceManager::get(); + for (const auto& customFileSource : fm->getCustomFileSources()) { + if (customFileSource->canRequest(resource)) { + return true; + } + } + return (assetFileSource && assetFileSource->canRequest(resource)) || (localFileSource && localFileSource->canRequest(resource)) || (databaseFileSource && databaseFileSource->canRequest(resource)) || diff --git a/platform/default/src/mbgl/storage/plugin_file_source.cpp b/platform/default/src/mbgl/storage/plugin_file_source.cpp new file mode 100644 index 000000000000..163542eeef30 --- /dev/null +++ b/platform/default/src/mbgl/storage/plugin_file_source.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { + +void PluginFileSource::setProtocolPrefix([[maybe_unused]] const std::string& protocolPrefix) {} + +class PluginFileSource::Impl { +public: + explicit Impl(const ActorRef&, const ResourceOptions& resourceOptions_, const ClientOptions& clientOptions_) + : resourceOptions(resourceOptions_.clone()), + clientOptions(clientOptions_.clone()) {} + + OnRequestResource _requestFunction; + void setOnRequestResourceFunction(OnRequestResource requestFunction) { _requestFunction = requestFunction; } + + void request(const Resource& resource, const ActorRef& req) { + Response response; + if (_requestFunction) { + response = _requestFunction(resource); + } else { + response.error = std::make_unique( + Response::Error::Reason::Other, std::string("Custom Protocol Handler Not Configured Correctly")); + } + req.invoke(&FileSourceRequest::setResponse, response); + } + + void setResourceOptions(ResourceOptions options) { + std::lock_guard lock(resourceOptionsMutex); + resourceOptions = options; + } + + ResourceOptions getResourceOptions() { + std::lock_guard lock(resourceOptionsMutex); + return resourceOptions.clone(); + } + + void setClientOptions(ClientOptions options) { + std::lock_guard lock(clientOptionsMutex); + clientOptions = options; + } + + ClientOptions getClientOptions() { + std::lock_guard lock(clientOptionsMutex); + return clientOptions.clone(); + } + +private: + mutable std::mutex resourceOptionsMutex; + mutable std::mutex clientOptionsMutex; + ResourceOptions resourceOptions; + ClientOptions clientOptions; +}; + +void PluginFileSource::setOnCanRequestFunction(OnCanRequestResource requestFunction) { + _onCanRequestResourceFunction = requestFunction; +} + +void PluginFileSource::setOnRequestResourceFunction(OnRequestResource requestFunction) { + impl.get()->actor().invoke(&Impl::setOnRequestResourceFunction, requestFunction); +} + +PluginFileSource::PluginFileSource(const ResourceOptions& resourceOptions, const ClientOptions& clientOptions) + : impl(std::make_unique>( + util::makeThreadPrioritySetter(platform::EXPERIMENTAL_THREAD_PRIORITY_FILE), + "PluginFileSource", + resourceOptions.clone(), + clientOptions.clone())) {} + +PluginFileSource::~PluginFileSource() = default; + +std::unique_ptr PluginFileSource::request(const Resource& resource, Callback callback) { + auto req = std::make_unique(std::move(callback)); + + impl->actor().invoke(&Impl::request, resource, req->actor()); + + return req; +} + +bool PluginFileSource::canRequest(const Resource& resource) const { + if (_onCanRequestResourceFunction) { + return _onCanRequestResourceFunction(resource); + } + return false; +} + +void PluginFileSource::pause() { + impl->pause(); +} + +void PluginFileSource::resume() { + impl->resume(); +} + +void PluginFileSource::setResourceOptions(ResourceOptions options) { + impl->actor().invoke(&Impl::setResourceOptions, options.clone()); +} + +ResourceOptions PluginFileSource::getResourceOptions() { + return impl->actor().ask(&Impl::getResourceOptions).get(); +} + +void PluginFileSource::setClientOptions(ClientOptions options) { + impl->actor().invoke(&Impl::setClientOptions, options.clone()); +} + +ClientOptions PluginFileSource::getClientOptions() { + return impl->actor().ask(&Impl::getClientOptions).get(); +} + +} // namespace mbgl diff --git a/platform/ios/VERSION b/platform/ios/VERSION index 7a33340e0e39..3a8e58e6b3a9 100644 --- a/platform/ios/VERSION +++ b/platform/ios/VERSION @@ -1 +1 @@ -6.19.3 +6.19.4 diff --git a/platform/ios/app-swift/Data/.gitignore b/platform/ios/app-swift/Data/.gitignore new file mode 100644 index 000000000000..7e3da378d373 --- /dev/null +++ b/platform/ios/app-swift/Data/.gitignore @@ -0,0 +1,4 @@ +*.glb +*.metal +*.txt +*.json diff --git a/platform/ios/app/MBXViewController.mm b/platform/ios/app/MBXViewController.mm index b0e1031b77b3..f179dda8205f 100644 --- a/platform/ios/app/MBXViewController.mm +++ b/platform/ios/app/MBXViewController.mm @@ -27,6 +27,8 @@ #import "PluginLayerExample.h" #import "PluginLayerExampleMetalRendering.h" #import "MLNPluginStyleLayer.h" +#import "PluginProtocolExample.h" +#import "StyleFilterExample.h" static const CLLocationCoordinate2D WorldTourDestinations[] = { { .latitude = 38.8999418, .longitude = -77.033996 }, @@ -281,6 +283,8 @@ -(void)addPluginLayers { [self.mapView addPluginLayerType:[PluginLayerExample class]]; [self.mapView addPluginLayerType:[PluginLayerExampleMetalRendering class]]; + [self.mapView addPluginProtocolHandler:[PluginProtocolExample class]]; + [self.mapView addStyleFilter:[[StyleFilterExample alloc] init]]; } @@ -358,6 +362,26 @@ - (void)viewDidLoad } } }]; + /* This is just a test of adding a plugin at runtime via style layer + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self createPluginStyleLayer]; + }); + */ + +} + +-(void)createPluginStyleLayer { + + MLNPluginStyleLayer *layer = [[MLNPluginStyleLayer alloc] initWithType:@"maplibre::filter_features" + layerIdentifier:@"centroid-features" + layerPropeties:@{ + @"source": @"maplibre", + @"source-layer": @"countries", + @"maxzoom": @(24), + @"minzoom": @(1) + }]; + [self.mapView.style addLayer:layer]; + } - (UIInterfaceOrientationMask)supportedInterfaceOrientations @@ -2333,19 +2357,22 @@ - (void)setStyles self.styleNames = [NSMutableArray array]; self.styleURLs = [NSMutableArray array]; - - - /// Style that does not require an `apiKey` nor any further configuration [self.styleNames addObject:@"MapLibre Basic"]; [self.styleURLs addObject:[NSURL URLWithString:@"https://demotiles.maplibre.org/style.json"]]; - /// This is hte same style as above but copied locally and the three instances of the metal plug-in layer added to the style + /// This is the same style as above but copied locally and the three instances of the metal plug-in layer added to the style /// Look for "type": "plugin-layer-metal-rendering" in the PluginLayerTestStyle.json for an example of how the layer is defined [self.styleNames addObject:@"MapLibre Basic - Local With Plugin"]; NSURL *url = [[NSBundle mainBundle] URLForResource:@"PluginLayerTestStyle.json" withExtension:nil]; [self.styleURLs addObject:url]; + /// This is the same style as above, but using the plugin protocol to actually load the style + [self.styleNames addObject:@"MapLibre Basic - Local With Plugin Loader"]; + NSURL *pluginstyleurl = [NSURL URLWithString:@"pluginProtocol://PluginLayerTestStyle.json"]; + [self.styleURLs addObject:pluginstyleurl]; + + /// Add MapLibre Styles if an `apiKey` exists NSString* apiKey = [MLNSettings apiKey]; if (apiKey.length) diff --git a/platform/ios/bazel/files.bzl b/platform/ios/bazel/files.bzl index 4667a1da83ed..c93bd2cadf70 100644 --- a/platform/ios/bazel/files.bzl +++ b/platform/ios/bazel/files.bzl @@ -42,6 +42,7 @@ MLN_IOS_PRIVATE_HEADERS = [ "src/MLNMapView+Impl.h", "src/MLNMapView+Metal.h", "src/MLNMapView+OpenGL.h", + "src/MLNLocationIndicatorUserLocationAnnotationView.h", ] MLN_IOS_PUBLIC_OBJC_SOURCE = [ @@ -63,6 +64,7 @@ MLN_IOS_PUBLIC_OBJCPP_SOURCE = [ "src/MLNAnnotationView.mm", "src/MLNCompassButton.mm", "src/MLNFaux3DUserLocationAnnotationView.mm", + "src/MLNLocationIndicatorUserLocationAnnotationView.mm", "src/MLNMapAccessibilityElement.mm", "src/MLNMapProjection.mm", "src/MLNMapView.mm", diff --git a/platform/ios/resources/Images.xcassets/user_bearing_icon.imageset/Contents.json b/platform/ios/resources/Images.xcassets/user_bearing_icon.imageset/Contents.json new file mode 100644 index 000000000000..525f85fd1cd2 --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_bearing_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "user_bearing_icon.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/resources/Images.xcassets/user_bearing_icon.imageset/user_bearing_icon.svg b/platform/ios/resources/Images.xcassets/user_bearing_icon.imageset/user_bearing_icon.svg new file mode 100644 index 000000000000..ba7f4c47f3ac --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_bearing_icon.imageset/user_bearing_icon.svg @@ -0,0 +1,9 @@ + + + diff --git a/platform/ios/resources/Images.xcassets/user_icon.imageset/Contents.json b/platform/ios/resources/Images.xcassets/user_icon.imageset/Contents.json new file mode 100644 index 000000000000..be1b4bcb7e62 --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "user_icon.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/resources/Images.xcassets/user_icon.imageset/user_icon.svg b/platform/ios/resources/Images.xcassets/user_icon.imageset/user_icon.svg new file mode 100644 index 000000000000..fcac127bd46e --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_icon.imageset/user_icon.svg @@ -0,0 +1,9 @@ + + + diff --git a/platform/ios/resources/Images.xcassets/user_icon_shadow.imageset/Contents.json b/platform/ios/resources/Images.xcassets/user_icon_shadow.imageset/Contents.json new file mode 100644 index 000000000000..f6592a561e1f --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_icon_shadow.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "user_icon_shadow.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/resources/Images.xcassets/user_icon_shadow.imageset/user_icon_shadow.png b/platform/ios/resources/Images.xcassets/user_icon_shadow.imageset/user_icon_shadow.png new file mode 100644 index 000000000000..5ea50fe5c96d Binary files /dev/null and b/platform/ios/resources/Images.xcassets/user_icon_shadow.imageset/user_icon_shadow.png differ diff --git a/platform/ios/resources/Images.xcassets/user_icon_stale.imageset/Contents.json b/platform/ios/resources/Images.xcassets/user_icon_stale.imageset/Contents.json new file mode 100644 index 000000000000..e08508836c39 --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_icon_stale.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "user_icon_stale.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/resources/Images.xcassets/user_icon_stale.imageset/user_icon_stale.svg b/platform/ios/resources/Images.xcassets/user_icon_stale.imageset/user_icon_stale.svg new file mode 100644 index 000000000000..42361ea6dfe2 --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_icon_stale.imageset/user_icon_stale.svg @@ -0,0 +1,9 @@ + + + diff --git a/platform/ios/resources/Images.xcassets/user_puck_icon.imageset/Contents.json b/platform/ios/resources/Images.xcassets/user_puck_icon.imageset/Contents.json new file mode 100644 index 000000000000..1df1fea05642 --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_puck_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "user_puck_icon.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/resources/Images.xcassets/user_puck_icon.imageset/user_puck_icon.svg b/platform/ios/resources/Images.xcassets/user_puck_icon.imageset/user_puck_icon.svg new file mode 100644 index 000000000000..cb26bc7bee7d --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_puck_icon.imageset/user_puck_icon.svg @@ -0,0 +1,18 @@ + + + + + diff --git a/platform/ios/resources/Images.xcassets/user_stroke_icon.imageset/Contents.json b/platform/ios/resources/Images.xcassets/user_stroke_icon.imageset/Contents.json new file mode 100644 index 000000000000..7c83043a811f --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_stroke_icon.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "user_stroke_icon.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/platform/ios/resources/Images.xcassets/user_stroke_icon.imageset/user_stroke_icon.svg b/platform/ios/resources/Images.xcassets/user_stroke_icon.imageset/user_stroke_icon.svg new file mode 100644 index 000000000000..1be237b6452c --- /dev/null +++ b/platform/ios/resources/Images.xcassets/user_stroke_icon.imageset/user_stroke_icon.svg @@ -0,0 +1,9 @@ + + + diff --git a/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm b/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm index 72691ed2688d..65fafe34f3ae 100644 --- a/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm +++ b/platform/ios/src/MLNFaux3DUserLocationAnnotationView.mm @@ -259,6 +259,11 @@ - (void)drawPuck [self.layer addSublayer:_puckArrow]; } + else if (!CGColorEqualToColor(_puckArrow.fillColor, [arrowColor CGColor])) + { + _puckArrow.fillColor = [arrowColor CGColor]; + _puckArrow.strokeColor = [arrowColor CGColor]; + } if (self.userLocation.location.course >= 0) { _puckArrow.affineTransform = CGAffineTransformRotate(CGAffineTransformIdentity, -MLNRadiansFromDegrees(self.mapView.direction - self.userLocation.location.course)); @@ -461,6 +466,10 @@ - (void)drawDot [self.layer addSublayer:_haloLayer]; } + else if (!CGColorEqualToColor(_haloLayer.backgroundColor, [haloColor CGColor])) + { + _haloLayer.backgroundColor = [haloColor CGColor]; + } // background dot (white with black shadow) // @@ -513,6 +522,10 @@ - (void)drawDot [self.layer addSublayer:_dotLayer]; } + else if (!CGColorEqualToColor(_dotLayer.backgroundColor, [puckBackgroundColor CGColor])) + { + _dotLayer.backgroundColor = [puckBackgroundColor CGColor]; + } if (_puckModeActivated) { diff --git a/platform/ios/src/MLNLocationIndicatorUserLocationAnnotationView.h b/platform/ios/src/MLNLocationIndicatorUserLocationAnnotationView.h new file mode 100644 index 000000000000..edc4843e6bbc --- /dev/null +++ b/platform/ios/src/MLNLocationIndicatorUserLocationAnnotationView.h @@ -0,0 +1,15 @@ +#import +#import "MLNUserLocationAnnotationView.h" + +typedef NS_ENUM(NSUInteger, MLNRenderMode) { + MLNRenderModeNone = 0, + MLNRenderModeCompass, + MLNRenderModeGps, + MLNRenderModeNormal, +}; + +@interface MLNLocationIndicatorUserLocationAnnotationView : MLNUserLocationAnnotationView + +- (void)removeLayer; + +@end diff --git a/platform/ios/src/MLNLocationIndicatorUserLocationAnnotationView.mm b/platform/ios/src/MLNLocationIndicatorUserLocationAnnotationView.mm new file mode 100644 index 000000000000..99b28b6e4f19 --- /dev/null +++ b/platform/ios/src/MLNLocationIndicatorUserLocationAnnotationView.mm @@ -0,0 +1,131 @@ +#import "MLNLocationIndicatorUserLocationAnnotationView.h" +#include +#include +#include + +#import "MLNLocationIndicatorStyleLayer.h" +#import "MLNUserLocation.h" +#import "UIImage+MLNAdditions.h" +#import "MLNMapView.h" +#import "NSExpression+MLNAdditions.h" + +const CGFloat MLNUserLocationAnnotationPuckSize = 45.0; +const CGFloat MLNUserLocationAnnotationArrowSize = MLNUserLocationAnnotationPuckSize * 0.5; + + +@implementation MLNLocationIndicatorUserLocationAnnotationView { + MLNLocationIndicatorStyleLayer* location_layer; + + CLLocation* _oldLocation; + MLNRenderMode _renderMode; +} + +- (void)update { + if (self.mapView.style && !self->location_layer) { + // Find existing location indicator layer before creating a new one + self->location_layer = (MLNLocationIndicatorStyleLayer*)[self.mapView.style layerWithIdentifier:@"location_indicator_layer"]; + + if (!self->location_layer) { + self->_renderMode = MLNRenderModeNone; + + self->location_layer = [[MLNLocationIndicatorStyleLayer alloc] initWithIdentifier:@"location_indicator_layer"]; + + [self.mapView.style setImage:[UIImage mgl_resourceImageNamed:@"user_bearing_icon"] forName:@"user_bearing_icon"]; + [self.mapView.style setImage:[UIImage mgl_resourceImageNamed:@"user_icon_stale"] forName:@"user_icon_stale"]; + [self.mapView.style setImage:[UIImage mgl_resourceImageNamed:@"user_icon"] forName:@"user_icon"]; + [self.mapView.style setImage:[UIImage mgl_resourceImageNamed:@"user_puck_icon"] forName:@"user_puck_icon"]; + [self.mapView.style setImage:[UIImage mgl_resourceImageNamed:@"user_stroke_icon"] forName:@"user_stroke_icon"]; + [self.mapView.style setImage:[UIImage mgl_resourceImageNamed:@"user_icon_shadow"] forName:@"user_icon_shadow"]; + + NSDictionary *opacityStops = @{@(self.mapView.minimumZoomLevel): @0.6f, + @(self.mapView.maximumZoomLevel): @1.0f }; + NSExpression *stops = [NSExpression expressionForConstantValue:opacityStops]; + NSExpression *styleScaling = [NSExpression mgl_expressionForInterpolatingExpression:NSExpression.zoomLevelVariableExpression withCurveType:MLNExpressionInterpolationModeLinear parameters:nil stops:stops]; + self->location_layer.topImageSize = styleScaling; + self->location_layer.bearingImageSize = styleScaling; + + NSDictionary *backgroundOpacityStops = @{@(self.mapView.minimumZoomLevel): @1.2f, + @(self.mapView.maximumZoomLevel): @1.5f }; + NSExpression *backgroundStops = [NSExpression expressionForConstantValue:backgroundOpacityStops]; + NSExpression *backgroundStyleScaling = [NSExpression mgl_expressionForInterpolatingExpression:NSExpression.zoomLevelVariableExpression withCurveType:MLNExpressionInterpolationModeLinear parameters:nil stops:backgroundStops]; + self->location_layer.shadowImageSize = backgroundStyleScaling; + self->location_layer.imageTiltDisplacement = [NSExpression expressionForConstantValue:@2.0]; + self->location_layer.perspectiveCompensation = [NSExpression expressionForConstantValue:@0.9]; + + self->location_layer.bearingTransition = MLNTransitionMake(1.0, 0, kCAMediaTimingFunctionLinear); + + [self.mapView.style addLayer:self->location_layer]; + } + } + + CLLocation* newLocation = self.userLocation.location; + + if (self->location_layer) { + if (self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithCourse) { + if (self.userLocation.location.course >= 0) { + self->location_layer.bearing = [NSExpression expressionForConstantValue:@(self.userLocation.location.course)]; + } + + if (self->_renderMode != MLNRenderModeGps) { + self->location_layer.bearingImage = [NSExpression expressionForConstantValue:@"user_puck_icon"]; + self->location_layer.topImage = [NSExpression expressionForConstantValue:@"user_puck_icon"]; + self->location_layer.shadowImage = [NSExpression expressionForConstantValue:@"user_stroke_icon"]; + + self->_renderMode = MLNRenderModeGps; + } + } else { + if (self->_renderMode != MLNRenderModeNormal && self.mapView.userTrackingMode == MLNUserTrackingModeFollow) { + self->location_layer.bearingImage = self.mapView.showsUserHeadingIndicator ? [NSExpression expressionForConstantValue:@"user_bearing_icon"] : [NSExpression expressionForConstantValue:@"user_icon"]; + self->location_layer.topImage = [NSExpression expressionForConstantValue:@"user_icon"]; + self->location_layer.shadowImage = [NSExpression expressionForConstantValue:@"user_stroke_icon"]; + + self->_renderMode = MLNRenderModeNormal; + } else if (self->_renderMode != MLNRenderModeCompass && self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithHeading) { + self->location_layer.bearingImage = [NSExpression expressionForConstantValue:@"user_bearing_icon"]; + self->location_layer.topImage = [NSExpression expressionForConstantValue:@"user_icon"]; + self->location_layer.shadowImage = [NSExpression expressionForConstantValue:@"user_stroke_icon"]; + + self->_renderMode = MLNRenderModeCompass; + } + bool showHeading = self.mapView.showsUserHeadingIndicator || self.mapView.userTrackingMode == MLNUserTrackingModeFollowWithHeading; + if (showHeading) { + CLLocationDirection headingDirection = (self.userLocation.heading.trueHeading >= 0 ? self.userLocation.heading.trueHeading : self.userLocation.heading.magneticHeading); + if (headingDirection >= 0) { + self->location_layer.bearing = [NSExpression expressionForConstantValue:@(headingDirection)]; + } + } + } + + NSTimeInterval duration = 0.3; + if(_oldLocation) { + duration = MIN([newLocation.timestamp timeIntervalSinceDate:_oldLocation.timestamp], 1.0); + } + if (duration > 0) { + MLNTransition transition = MLNTransitionMake(duration, 0, kCAMediaTimingFunctionLinear); + self->location_layer.locationTransition = transition; + } + + NSArray *location = @[@(newLocation.coordinate.latitude), @(newLocation.coordinate.longitude), @0]; + self->location_layer.location = [NSExpression expressionForConstantValue:location]; + } + + _oldLocation = newLocation; +} + +- (void)removeLayer { + if (self->location_layer) { + if ([self.mapView.style layerWithIdentifier:@"location_indicator_layer"]) { + [self.mapView.style removeLayer:self->location_layer]; + } + self->location_layer = nil; + } + + [super removeFromSuperview]; +} + +- (void)removeFromSuperview { + [self removeLayer]; + [super removeFromSuperview]; +} + +@end diff --git a/platform/ios/src/MLNMapView.h b/platform/ios/src/MLNMapView.h index 727ea0610d5f..91413247584e 100644 --- a/platform/ios/src/MLNMapView.h +++ b/platform/ios/src/MLNMapView.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @class MLNScaleBar; @class MLNShape; @class MLNPluginLayer; +@class MLNStyleFilter; @protocol MLNMapViewDelegate; @protocol MLNAnnotation; @@ -532,6 +533,8 @@ MLN_EXPORT */ @property (nonatomic, assign) double tileLodZoomShift; +@property (nonatomic, assign) UIEdgeInsets frustumOffset; + // MARK: Displaying the User’s Location /** @@ -580,11 +583,23 @@ MLN_EXPORT @property (nonatomic, assign) BOOL showsUserLocation; /** - A boolean value indicating whether camera animation duration is set based - on the time difference between the last location update and the current one - or the default animation duration of 1 second. + A boolean value indicating whether the location indicator is drawn using the location + indicator layer or the location indication annotation. + */ +@property (nonatomic, assign) BOOL useLocationIndicatorLayer; - The default value of this property is `NO` +/** + A boolean value indicating whether the camera allows for concurrent animations. This is + a temporary feature flag to avoid breaking existing functionality. + */ +@property (nonatomic, assign) BOOL concurrentAnimations; + +/** +A boolean value indicating whether camera animation duration is set based +on the time difference between the last location update and the current one +or the default animation duration of 1 second. + +The default value of this property is `NO` */ @property (nonatomic, assign) BOOL dynamicNavigationCameraAnimationDuration; @@ -698,6 +713,13 @@ MLN_EXPORT */ - (void)updateUserLocationAnnotationViewAnimatedWithDuration:(NSTimeInterval)duration; +/** + Creates or updates the user location annotation view. This method calls the + `mapView:viewForAnnotation:` delegate method to obtain a custom view. If no custom view is + provided, it defaults to a native one. + */ +- (void)createUserLocationAnnotationView; + /** A Boolean value indicating whether the user location annotation may display a permanent heading indicator. @@ -1660,6 +1682,16 @@ vertically on the map. animated:(BOOL)animated completionHandler:(nullable void (^)(void))completion; +/** + Toggling between the Transform and the TransformActive implementation. + + It allows us to switch between the two implementations at runtime. + + It also resets the current transform state so be careful when using it + in the middle of a transformation. + */ +- (void)toggleTransform; + // MARK: Converting Geographic Coordinates /** @@ -2303,6 +2335,16 @@ vertically on the map. */ - (void)addPluginLayerType:(Class)pluginLayerClass; +/** + Adds a plug-in protocol handler that is external to this library + */ +- (void)addPluginProtocolHandler:(Class)pluginProtocolHandlerClass; + +/** + Adds a style filter to the map view + */ +- (void)addStyleFilter:(MLNStyleFilter *)styleFilter; + @end NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/MLNMapView.mm b/platform/ios/src/MLNMapView.mm index 8a6b9542e300..d6eb5688fecb 100644 --- a/platform/ios/src/MLNMapView.mm +++ b/platform/ios/src/MLNMapView.mm @@ -30,9 +30,13 @@ #include #include #include +#include +#include +#include #include #include #include +#include #import "Mapbox.h" #import "MLNShape_Private.h" @@ -80,8 +84,12 @@ #import "MLNActionJournalOptions_Private.h" #import "MLNMapProjection.h" #import "MLNPluginLayer.h" +#import "MLNPluginProtocolHandler.h" #import "MLNStyleLayerManager.h" #include "MLNPluginStyleLayer_Private.h" +#import "MLNLocationIndicatorUserLocationAnnotationView.h" +#include "MLNStyleFilter.h" +#include "MLNStyleFilter_Private.h" #include #include @@ -452,8 +460,12 @@ @interface MLNMapView () getTileLodZoomShift(); } +-(void)setFrustumOffset:(UIEdgeInsets)frustomOffset +{ + _mbglMap->setFrustumOffset(MLNEdgeInsetsFromNSEdgeInsets(frustomOffset)); +} + +-(UIEdgeInsets)frustumOffset +{ + return NSEdgeInsetsFromMLNEdgeInsets(_mbglMap->getFrustumOffset()); +} + // MARK: - Accessibility - - (NSString *)accessibilityValue @@ -4485,7 +4520,7 @@ - (void)_flyToCamera:(MLNMapCamera *)camera edgePadding:(UIEdgeInsets)insets wit } - (void)cancelTransitions { - if (!_mbglMap) + if (!_mbglMap || self.concurrentAnimations) { return; } @@ -6082,6 +6117,32 @@ - (NSString *)accuracyDescriptionString { return dictionary[@"MLNAccuracyAuthorizationDescription"]; } +- (void)createUserLocationAnnotationView { + MLNUserLocationAnnotationView *userLocationAnnotationView; + + if ([self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]) + { + userLocationAnnotationView = (MLNUserLocationAnnotationView *)[self.delegate mapView:self viewForAnnotation:self.userLocation]; + if (userLocationAnnotationView && ! [userLocationAnnotationView isKindOfClass:MLNUserLocationAnnotationView.class]) + { + [NSException raise:MLNUserLocationAnnotationTypeException + format:@"User location annotation view must be a kind of MLNUserLocationAnnotationView. %@", userLocationAnnotationView.debugDescription]; + } + } + + if (self.userLocationAnnotationView) { + [self.userLocationAnnotationView removeFromSuperview]; + } + self.userLocationAnnotationView = userLocationAnnotationView ?: self.useLocationIndicatorLayer ? [[MLNLocationIndicatorUserLocationAnnotationView alloc] init] : [[MLNFaux3DUserLocationAnnotationView alloc] init]; + self.userLocationAnnotationView.mapView = self; + self.userLocationAnnotationView.userLocation = self.userLocation; + + self.userLocationAnnotationView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | + UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); + + [self.userLocationAnnotationView update]; +} + - (void)setShowsUserLocation:(BOOL)showsUserLocation { MLNLogDebug(@"Setting showsUserLocation: %@", MLNStringFromBOOL(showsUserLocation)); @@ -6097,25 +6158,7 @@ - (void)setShowsUserLocation:(BOOL)showsUserLocation } self.userLocation = [[MLNUserLocation alloc] initWithMapView:self]; - - MLNUserLocationAnnotationView *userLocationAnnotationView; - - if ([self.delegate respondsToSelector:@selector(mapView:viewForAnnotation:)]) - { - userLocationAnnotationView = (MLNUserLocationAnnotationView *)[self.delegate mapView:self viewForAnnotation:self.userLocation]; - if (userLocationAnnotationView && ! [userLocationAnnotationView isKindOfClass:MLNUserLocationAnnotationView.class]) - { - [NSException raise:MLNUserLocationAnnotationTypeException - format:@"User location annotation view must be a kind of MLNUserLocationAnnotationView. %@", userLocationAnnotationView.debugDescription]; - } - } - - self.userLocationAnnotationView = userLocationAnnotationView ?: [[MLNFaux3DUserLocationAnnotationView alloc] init]; - self.userLocationAnnotationView.mapView = self; - self.userLocationAnnotationView.userLocation = self.userLocation; - - self.userLocationAnnotationView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | - UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); + [self createUserLocationAnnotationView]; [self validateLocationServices]; } @@ -7701,6 +7744,44 @@ - (void)triggerRepaint _mbglMap->triggerRepaint(); } +-(MLNPluginLayerTileFeature *)featureFromCore:(std::shared_ptr)feature { + + MLNPluginLayerTileFeature *tempResult = [[MLNPluginLayerTileFeature alloc] init]; + + NSMutableDictionary *tileProperties = [NSMutableDictionary dictionary]; + for (auto p: feature->_featureProperties) { + NSString *key = [NSString stringWithUTF8String:p.first.c_str()]; + NSString *value = [NSString stringWithUTF8String:p.second.c_str()]; + [tileProperties setObject:value forKey:key]; + } + + tempResult.featureProperties = [NSDictionary dictionaryWithDictionary:tileProperties]; + + NSMutableArray *featureCoordinates = [NSMutableArray array]; + for (auto & coordinateCollection: feature->_featureCoordinates) { + + for (auto & coordinate: coordinateCollection._coordinates) { + CLLocationCoordinate2D c = CLLocationCoordinate2DMake(coordinate._lat, coordinate._lon); + NSValue *value = [NSValue valueWithBytes:&c objCType:@encode(CLLocationCoordinate2D)]; + [featureCoordinates addObject:value]; + } + + } + // TODO: Need to figure out how we're going to handle multiple coordinate groups/etc + if ([featureCoordinates count] > 0) { + tempResult.featureCoordinates = [NSArray arrayWithArray:featureCoordinates]; + } + + tempResult.featureID = [NSString stringWithUTF8String:feature->_featureID.c_str()]; + + return tempResult; +} + +-(NSString *)tileIDToString:(mbgl::OverscaledTileID &)tileID { + NSString *tempResult = [NSString stringWithFormat:@"%i,%i,%i", tileID.canonical.z, tileID.canonical.x, tileID.canonical.y]; + return tempResult; +} + /** Adds a plug-in layer that is external to this library */ @@ -7725,6 +7806,13 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { pass3D = mbgl::style::LayerTypeInfo::Pass3D::Required; } + + // If we read tile features, then we need to set these things + if (capabilities.supportsReadingTileFeatures) { + tileKind = mbgl::style::LayerTypeInfo::TileKind::Geometry; + source = mbgl::style::LayerTypeInfo::Source::Required; + } + auto factory = std::make_unique(layerType, source, pass3D, @@ -7736,6 +7824,7 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { __weak MLNMapView *weakMapView = self; Class layerClass = pluginLayerClass; + factory->supportsFeatureCollectionBuckets = capabilities.supportsReadingTileFeatures; factory->setOnLayerCreatedEvent([layerClass, weakMapView, pluginLayerClass](mbgl::style::PluginLayer *pluginLayer) { //NSLog(@"Creating Plugin Layer: %@", layerClass); @@ -7829,6 +7918,58 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { } }); + // If this layer can read tile features, then setup that lambda + if (capabilities.supportsReadingTileFeatures) { + + pluginLayerImpl->setFeatureCollectionLoadedFunction([weakPlugInLayer, weakMapView](const std::shared_ptr featureCollection) { + + @autoreleasepool { + + MLNPluginLayerTileFeatureCollection *collection = [[MLNPluginLayerTileFeatureCollection alloc] init]; + + // Add the features + NSMutableArray *featureList = [NSMutableArray arrayWithCapacity:featureCollection->_features.size()]; + for (auto f: featureCollection->_features) { + [featureList addObject:[weakMapView featureFromCore:f]]; + } + collection.features = [NSArray arrayWithArray:featureList]; + collection.tileID = [weakMapView tileIDToString:featureCollection->_featureCollectionTileID]; + + + [weakPlugInLayer onFeatureCollectionLoaded:collection]; + + } + + }); + + pluginLayerImpl->setFeatureCollectionUnloadedFunction([weakPlugInLayer, weakMapView](const std::shared_ptr featureCollection) { + + @autoreleasepool { + + // TODO: Map these collections to local vars and maybe don't keep recreating it + + MLNPluginLayerTileFeatureCollection *collection = [[MLNPluginLayerTileFeatureCollection alloc] init]; + + // Add the features + NSMutableArray *featureList = [NSMutableArray arrayWithCapacity:featureCollection->_features.size()]; + for (auto f: featureCollection->_features) { + [featureList addObject:[weakMapView featureFromCore:f]]; + } + collection.features = [NSArray arrayWithArray:featureList]; + collection.tileID = [weakMapView tileIDToString:featureCollection->_featureCollectionTileID]; + + [weakPlugInLayer onFeatureCollectionUnloaded:collection]; + + } + + }); + + + + + + } + }); // TODO: Same question as above. Do we ever want to have a core only layer type? @@ -7838,6 +7979,169 @@ -(void)addPluginLayerType:(Class)pluginLayerClass { } +- (MLNPluginProtocolHandlerResource *)resourceFromCoreResource:(const mbgl::Resource &)resource { + + MLNPluginProtocolHandlerResource *tempResult = [[MLNPluginProtocolHandlerResource alloc] init]; + + // The URL of the request + tempResult.resourceURL = [NSString stringWithUTF8String:resource.url.c_str()]; + + // The kind of request + switch (resource.kind) { + case mbgl::Resource::Kind::Style: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindStyle; + break; + case mbgl::Resource::Kind::Source: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindSource; + break; + case mbgl::Resource::Kind::Tile: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindTile; + break; + case mbgl::Resource::Kind::Glyphs: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindGlyphs; + break; + case mbgl::Resource::Kind::SpriteImage: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindSpriteImage; + break; + case mbgl::Resource::Kind::Image: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindImage; + break; + case mbgl::Resource::Kind::SpriteJSON: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindSpriteJSON; + break; + default: + tempResult.resourceKind = MLNPluginProtocolHandlerResourceKindUnknown; + break; + } + + // The loading method + if (resource.loadingMethod == mbgl::Resource::LoadingMethod::CacheOnly) { + tempResult.loadingMethod = MLNPluginProtocolHandlerResourceLoadingMethodCacheOnly; + } else if (resource.loadingMethod == mbgl::Resource::LoadingMethod::NetworkOnly) { + tempResult.loadingMethod = MLNPluginProtocolHandlerResourceLoadingMethodNetworkOnly; + } else if (resource.loadingMethod == mbgl::Resource::LoadingMethod::All) { + tempResult.loadingMethod = MLNPluginProtocolHandlerResourceLoadingMethodAll; + } + + if (resource.tileData) { + auto td = *resource.tileData; + MLNTileData *tileData = [[MLNTileData alloc] init]; + tileData.tileURLTemplate = [NSString stringWithUTF8String:td.urlTemplate.c_str()]; + tileData.tilePixelRatio = td.pixelRatio; + tileData.tileX = td.x; + tileData.tileY = td.y; + tileData.tileZoom = td.z; + tempResult.tileData = tileData; + } + + // TODO: Figure out which other properties from resource should be passed along here +/* + Usage usage{Usage::Online}; + Priority priority{Priority::Regular}; + std::optional> dataRange = std::nullopt; + std::optional priorModified = std::nullopt; + std::optional priorExpires = std::nullopt; + std::optional priorEtag = std::nullopt; + std::shared_ptr priorData; + Duration minimumUpdateInterval{Duration::zero()}; + StoragePolicy storagePolicy{StoragePolicy::Permanent}; + */ + + return tempResult; + +} + +- (void)addPluginProtocolHandler:(Class)pluginProtocolHandlerClass { + + + MLNPluginProtocolHandler *handler = [[pluginProtocolHandlerClass alloc] init]; + if (!self.pluginProtocols) { + self.pluginProtocols = [NSMutableArray array]; + } + [self.pluginProtocols addObject:handler]; + + // TODO: Unclear if any of these options are needed for plugins + mbgl::ResourceOptions resourceOptions; + + // TODO: Unclear if any of the properties on clientOptions need to be set + mbgl::ClientOptions clientOptions; + + // Use weak here so there isn't a retain cycle + __weak MLNPluginProtocolHandler *weakHandler = handler; + __weak MLNMapView *weakSelf = self; + + std::shared_ptr pluginSource = std::make_shared(resourceOptions, clientOptions); + pluginSource->setOnRequestResourceFunction([weakHandler, weakSelf](const mbgl::Resource &resource) -> mbgl::Response { + mbgl::Response tempResult; + + __strong MLNPluginProtocolHandler *strongHandler = weakHandler; + if (strongHandler) { + + MLNPluginProtocolHandlerResource *res = [weakSelf resourceFromCoreResource:resource]; + + // TODO: Figure out what other fields in response need to be passed back from requestResource + MLNPluginProtocolHandlerResponse *response = [strongHandler requestResource:res]; + if (response.data) { + tempResult.data = std::make_shared((const char*)[response.data bytes], + [response.data length]); + } + } + + return tempResult; + + }); + + pluginSource->setOnCanRequestFunction([weakHandler, weakSelf](const mbgl::Resource &resource) -> bool{ + @autoreleasepool { + __strong MLNPluginProtocolHandler *strongHandler = weakHandler; + if (!strongHandler) { + return false; + } + + MLNPluginProtocolHandlerResource *res = [weakSelf resourceFromCoreResource:resource]; + BOOL tempResult = [strongHandler canRequestResource:res]; + + return tempResult; + + } + }); + + auto fileSourceManager = mbgl::FileSourceManager::get(); + fileSourceManager->registerCustomFileSource(pluginSource); + +} + +-(void)addStyleFilter:(MLNStyleFilter *)styleFilter { + + if (!self.styleFilters) { + self.styleFilters = [NSMutableArray array]; + } + [self.styleFilters addObject:styleFilter]; + + auto coreStyleFilter = std::make_shared(); + coreStyleFilter->_filterStyleFunction = [styleFilter](const std::string &filterData) -> const std::string { + + std::string tempResult; + + @autoreleasepool { + NSData *sourceData = [NSData dataWithBytesNoCopy:(void *)filterData.data() + length:filterData.size() + freeWhenDone:NO]; + NSData *filteredData = [styleFilter filterData:sourceData]; + tempResult = std::string((const char*)[filteredData bytes], [filteredData length]); + + } + return tempResult; + }; + + // Set the ivar + [styleFilter setFilter:coreStyleFilter]; + + _mbglMap->getStyle().addStyleFilter(coreStyleFilter); + +} + + - (NSArray*)getActionJournalLogFiles { const auto& actionJournal = _mbglMap->getActionJournal(); diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index ae1ee305761c..645481a0dbdd 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -34,6 +34,7 @@ FOUNDATION_EXPORT MLN_EXPORT const unsigned char MapboxVersionString[]; #import "MLNImageSource.h" #import "MLNLight.h" #import "MLNLineStyleLayer.h" +#import "MLNLocationIndicatorStyleLayer.h" #import "MLNLocationManager.h" #import "MLNLoggingConfiguration.h" #import "MLNMapCamera.h" diff --git a/platform/macos/src/MLNMapView+Impl.h b/platform/macos/src/MLNMapView+Impl.h index 07fd08cc21d5..619a440ab703 100644 --- a/platform/macos/src/MLNMapView+Impl.h +++ b/platform/macos/src/MLNMapView+Impl.h @@ -2,6 +2,8 @@ #import #import +#import "MLNBackendResource.h" + @class MLNMapView; typedef struct _CGLContextObject* CGLContextObj; @@ -23,6 +25,8 @@ class MLNMapViewImpl : public mbgl::MapObserver { // Called by the view delegate when it's time to render. void render(); + virtual MLNBackendResource* getObject() = 0; + // mbgl::MapObserver implementation void onCameraWillChange(mbgl::MapObserver::CameraChangeMode) override; void onCameraIsChanging() override; diff --git a/platform/macos/src/MLNMapView+Metal.h b/platform/macos/src/MLNMapView+Metal.h index f6d12e66c600..9704e27a28f6 100644 --- a/platform/macos/src/MLNMapView+Metal.h +++ b/platform/macos/src/MLNMapView+Metal.h @@ -37,6 +37,8 @@ class MLNMapViewMetalImpl final : public MLNMapViewImpl, mbgl::PremultipliedImage readStillImage() override; + MLNBackendResource* getObject() override; + private: bool presentsWithTransaction = false; }; diff --git a/platform/macos/src/MLNMapView+Metal.mm b/platform/macos/src/MLNMapView+Metal.mm index 5c4759cbda7b..06ed7392378b 100644 --- a/platform/macos/src/MLNMapView+Metal.mm +++ b/platform/macos/src/MLNMapView+Metal.mm @@ -158,3 +158,12 @@ void swap() override { // return readFramebuffer(mapView.framebufferSize); // TODO: RendererBackend::readFramebuffer return {}; } + +MLNBackendResource* MLNMapViewMetalImpl::getObject() { + auto& resource = getResource(); + auto renderPassDescriptor = resource.getRenderPassDescriptor().get(); + return [[MLNBackendResource alloc] initWithMTKView:resource.mtlView + device:resource.mtlView.device + renderPassDescriptor:[MTLRenderPassDescriptor renderPassDescriptor] + commandBuffer:resource.commandBuffer]; +} diff --git a/platform/macos/src/MLNMapView+OpenGL.h b/platform/macos/src/MLNMapView+OpenGL.h index fb882b7072bd..2daf3aaab9d9 100644 --- a/platform/macos/src/MLNMapView+OpenGL.h +++ b/platform/macos/src/MLNMapView+OpenGL.h @@ -38,4 +38,6 @@ class MLNMapViewOpenGLImpl final : public MLNMapViewImpl, mbgl::PremultipliedImage readStillImage() override; CGLContextObj getCGLContextObj() override; + + MLNBackendResource getObject() override; }; diff --git a/platform/macos/src/MLNMapView+OpenGL.mm b/platform/macos/src/MLNMapView+OpenGL.mm index de747afbcd3b..83409e00a359 100644 --- a/platform/macos/src/MLNMapView+OpenGL.mm +++ b/platform/macos/src/MLNMapView+OpenGL.mm @@ -87,3 +87,7 @@ void bind() override { MLNOpenGLLayer* layer = (MLNOpenGLLayer*)mapView.layer; return layer.openGLContext.CGLContextObj; } + +MLNBackendResource MLNMapViewOpenGLImpl::getObject() { + return MLNBackendResource(); +} \ No newline at end of file diff --git a/platform/macos/src/MLNMapView.h b/platform/macos/src/MLNMapView.h index 3894741317d8..d4d12afb3ded 100644 --- a/platform/macos/src/MLNMapView.h +++ b/platform/macos/src/MLNMapView.h @@ -2,6 +2,7 @@ #import #import +#import "MLNBackendResource.h" #import "MLNFoundation.h" #import "MLNGeometry.h" #import "MLNMapOptions.h" @@ -1336,6 +1337,7 @@ around the returned camera object if it were set as the receiver’s camera. */ - (void)clearActionJournalLog; +- (MLNBackendResource *)backendResource; @end NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/MLNMapView.mm b/platform/macos/src/MLNMapView.mm index 6dc79ba88bfa..853b2aa8ba54 100644 --- a/platform/macos/src/MLNMapView.mm +++ b/platform/macos/src/MLNMapView.mm @@ -3314,4 +3314,8 @@ - (void)clearActionJournalLog actionJournal->clearLog(); } +- (MLNBackendResource *)backendResource { + return _mbglView->getObject(); +} + @end diff --git a/render-test/android/.gitignore b/render-test/android/.gitignore index 51e9160c76c4..9fbaf0ceead5 100644 --- a/render-test/android/.gitignore +++ b/render-test/android/.gitignore @@ -2,3 +2,7 @@ .gradle app/build app/src/main/assets/data.zip +.project +.settings +.classpath +build/ diff --git a/shaders/symbol_icon.vertex.glsl b/shaders/symbol_icon.vertex.glsl index 678f3323b74f..a2c5dffd7328 100644 --- a/shaders/symbol_icon.vertex.glsl +++ b/shaders/symbol_icon.vertex.glsl @@ -29,6 +29,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -98,7 +99,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/shaders/symbol_sdf.vertex.glsl b/shaders/symbol_sdf.vertex.glsl index 6c94980d7afe..edf31d4ef31a 100644 --- a/shaders/symbol_sdf.vertex.glsl +++ b/shaders/symbol_sdf.vertex.glsl @@ -37,6 +37,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -122,7 +123,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = u_is_text_prop ? size / 24.0 : size; diff --git a/shaders/symbol_text_and_icon.vertex.glsl b/shaders/symbol_text_and_icon.vertex.glsl index 6035d4512770..3e0557d62065 100644 --- a/shaders/symbol_text_and_icon.vertex.glsl +++ b/shaders/symbol_text_and_icon.vertex.glsl @@ -36,6 +36,7 @@ layout (std140) uniform SymbolDrawableUBO { bool u_pitch_with_map; bool u_is_size_zoom_constant; bool u_is_size_feature_constant; + bool u_is_offset; highp float u_size_t; // used to interpolate between zoom stops when size is a composite function highp float u_size; // used when size is both zoom and feature constant @@ -121,7 +122,9 @@ void main() { 0.0, // Prevents oversized near-field symbols in pitched/overzoomed tiles 4.0); - size *= perspective_ratio; + if (!u_is_offset) { + size *= perspective_ratio; + } float fontScale = size / 24.0; diff --git a/src/mbgl/gfx/scissor_rect.hpp b/src/mbgl/gfx/scissor_rect.hpp new file mode 100644 index 000000000000..7e21f9cb0b32 --- /dev/null +++ b/src/mbgl/gfx/scissor_rect.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace mbgl { +namespace gfx { + +class ScissorRect { +public: + int32_t x, y; + uint32_t width, height; + + bool operator==(const ScissorRect& other) const { + return x == other.x && y == other.y && width == other.width && height == other.height; + } + + bool operator!=(const ScissorRect& other) const { return !(*this == other); } +}; + +} // namespace gfx +} // namespace mbgl diff --git a/src/mbgl/gfx/symbol_drawable_data.hpp b/src/mbgl/gfx/symbol_drawable_data.hpp index 6688b39ecaa1..fd7e3be04724 100644 --- a/src/mbgl/gfx/symbol_drawable_data.hpp +++ b/src/mbgl/gfx/symbol_drawable_data.hpp @@ -19,14 +19,16 @@ struct SymbolDrawableData : public DrawableData { const style::AlignmentType pitchAlignment_, const style::AlignmentType rotationAlignment_, const style::SymbolPlacementType placement_, - const style::IconTextFitType textFit_) + const style::IconTextFitType textFit_, + const bool isOffset_) : isHalo(isHalo_), bucketVariablePlacement(bucketVariablePlacement_), symbolType(symbolType_), pitchAlignment(pitchAlignment_), rotationAlignment(rotationAlignment_), placement(placement_), - textFit(textFit_) {} + textFit(textFit_), + isOffset(isOffset_) {} ~SymbolDrawableData() override = default; const bool isHalo; @@ -36,6 +38,7 @@ struct SymbolDrawableData : public DrawableData { const style::AlignmentType rotationAlignment; const style::SymbolPlacementType placement; const style::IconTextFitType textFit; + const bool isOffset; }; using UniqueSymbolDrawableData = std::unique_ptr; diff --git a/src/mbgl/gl/context.cpp b/src/mbgl/gl/context.cpp index b5a3856bd5e5..952233ccfde8 100644 --- a/src/mbgl/gl/context.cpp +++ b/src/mbgl/gl/context.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -463,6 +464,7 @@ void Context::resetState(gfx::DepthMode depthMode, gfx::ColorMode colorMode) { setStencilMode(gfx::StencilMode::disabled()); setColorMode(colorMode); setCullFaceMode(gfx::CullFaceMode::disabled()); + setScissorTest({0, 0, 0, 0}); } bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& buffer, @@ -495,7 +497,7 @@ void Context::unbindGlobalUniformBuffers(gfx::RenderPass&) const noexcept { void Context::setDirtyState() { MLN_TRACE_FUNC(); - // Note: does not set viewport/scissorTest/bindFramebuffer to dirty + // Note: does not set viewport/bindFramebuffer to dirty // since they are handled separately in the view object. stencilFunc.setDirty(); stencilMask.setDirty(); @@ -518,6 +520,7 @@ void Context::setDirtyState() { cullFace.setDirty(); cullFaceSide.setDirty(); cullFaceWinding.setDirty(); + scissorTest.setDirty(); program.setDirty(); lineWidth.setDirty(); activeTextureUnit.setDirty(); @@ -642,6 +645,10 @@ void Context::setCullFaceMode(const gfx::CullFaceMode& mode) { cullFaceWinding = mode.winding; } +void Context::setScissorTest(const gfx::ScissorRect& rect) { + scissorTest = rect; +} + void Context::setDepthMode(const gfx::DepthMode& depth) { if (depth.func == gfx::DepthFunctionType::Always && depth.mask != gfx::DepthMaskType::ReadWrite) { depthTest = false; diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index d7757e15c120..a94038df31d6 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -77,6 +78,7 @@ class Context final : public gfx::Context { void setStencilMode(const gfx::StencilMode&); void setColorMode(const gfx::ColorMode&); void setCullFaceMode(const gfx::CullFaceMode&); + void setScissorTest(const gfx::ScissorRect&); void draw(const gfx::DrawMode&, std::size_t indexOffset, std::size_t indexLength); diff --git a/src/mbgl/gl/drawable_gl.cpp b/src/mbgl/gl/drawable_gl.cpp index f2f9e2cfe060..7fca47b45763 100644 --- a/src/mbgl/gl/drawable_gl.cpp +++ b/src/mbgl/gl/drawable_gl.cpp @@ -60,6 +60,8 @@ void DrawableGL::draw(PaintParameters& parameters) const { context.setColorMode(getColorMode()); context.setCullFaceMode(getCullFaceMode()); + context.setScissorTest(parameters.scissorRect); + impl->uniformBuffers.bind(); bindTextures(); diff --git a/src/mbgl/gl/offscreen_texture.cpp b/src/mbgl/gl/offscreen_texture.cpp index e0da20dfce1b..90f71b4146bd 100644 --- a/src/mbgl/gl/offscreen_texture.cpp +++ b/src/mbgl/gl/offscreen_texture.cpp @@ -33,7 +33,7 @@ class OffscreenTextureResource final : public gl::RenderableResource { } context.activeTextureUnit = 0; - context.scissorTest = false; + context.scissorTest = {0, 0, 0, 0}; context.viewport = {.x = 0, .y = 0, .size = size}; } diff --git a/src/mbgl/gl/renderer_backend.cpp b/src/mbgl/gl/renderer_backend.cpp index 3a8a31b9d3c4..cb93e9d33a1a 100644 --- a/src/mbgl/gl/renderer_backend.cpp +++ b/src/mbgl/gl/renderer_backend.cpp @@ -53,10 +53,10 @@ void RendererBackend::assumeViewport(int32_t x, int32_t y, const Size& size) { assert(gl::value::Viewport::Get() == getContext().viewport.getCurrentValue()); } -void RendererBackend::assumeScissorTest(bool enabled) { +void RendererBackend::assumeScissorTest(int32_t x, int32_t y, uint32_t width, uint32_t height) { MLN_TRACE_FUNC(); - getContext().scissorTest.setCurrentValue(enabled); + getContext().scissorTest.setCurrentValue({x, y, width, height}); assert(gl::value::ScissorTest::Get() == getContext().scissorTest.getCurrentValue()); } @@ -82,10 +82,10 @@ void RendererBackend::setViewport(int32_t x, int32_t y, const Size& size) { assert(gl::value::Viewport::Get() == getContext().viewport.getCurrentValue()); } -void RendererBackend::setScissorTest(bool enabled) { +void RendererBackend::setScissorTest(int32_t x, int32_t y, uint32_t width, uint32_t height) { MLN_TRACE_FUNC(); - getContext().scissorTest = enabled; + getContext().scissorTest = {x, y, width, height}; assert(gl::value::ScissorTest::Get() == getContext().scissorTest.getCurrentValue()); } diff --git a/src/mbgl/gl/value.cpp b/src/mbgl/gl/value.cpp index fe0840c723cb..0aa200e00d0f 100644 --- a/src/mbgl/gl/value.cpp +++ b/src/mbgl/gl/value.cpp @@ -358,15 +358,17 @@ const constexpr ScissorTest::Type ScissorTest::Default; void ScissorTest::Set(const Type& value) { MLN_TRACE_ZONE(ScissorTest::Set); MLN_TRACE_FUNC_GL(); - MBGL_CHECK_ERROR(value ? glEnable(GL_SCISSOR_TEST) : glDisable(GL_SCISSOR_TEST)); + bool enabled = value.x != 0 || value.y != 0 || value.width != 0 || value.height != 0; + MBGL_CHECK_ERROR(enabled ? glEnable(GL_SCISSOR_TEST) : glDisable(GL_SCISSOR_TEST)); + MBGL_CHECK_ERROR(glScissor(value.x, value.y, value.width, value.height)); } ScissorTest::Type ScissorTest::Get() { MLN_TRACE_ZONE(ScissorTest::Get); MLN_TRACE_FUNC_GL(); - Type scissorTest; - MBGL_CHECK_ERROR(scissorTest = glIsEnabled(GL_SCISSOR_TEST)); - return scissorTest; + GLint scissor[4]; + MBGL_CHECK_ERROR(glGetIntegerv(GL_SCISSOR_BOX, scissor)); + return {scissor[0], scissor[1], static_cast(scissor[2]), static_cast(scissor[3])}; } const constexpr BindFramebuffer::Type BindFramebuffer::Default; diff --git a/src/mbgl/gl/value.hpp b/src/mbgl/gl/value.hpp index cea3136a008f..22cdbdfc56c9 100644 --- a/src/mbgl/gl/value.hpp +++ b/src/mbgl/gl/value.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -189,8 +190,8 @@ struct Viewport { }; struct ScissorTest { - using Type = bool; - static const constexpr Type Default = false; + using Type = gfx::ScissorRect; + static const constexpr Type Default = {0, 0, 0, 0}; static void Set(const Type&); static Type Get(); }; @@ -238,6 +239,13 @@ struct CullFaceWinding { static Type Get(); }; +struct ScissorRect { + using Type = gfx::ScissorRect; + static const constexpr Type Default = {0, 0, 0, 0}; + static void Set(const Type&); + static Type Get(); +}; + struct BindTexture { using Type = gl::TextureID; static const constexpr Type Default = 0; diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 3f2b3c298e96..fe87cbb1ca8d 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -16,12 +17,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include namespace mbgl { @@ -86,7 +89,7 @@ void Map::renderStill(StillImageCallback callback) { void Map::renderStill(const CameraOptions& camera, MapDebugOptions debugOptions, StillImageCallback callback) { impl->cameraMutated = true; impl->debugOptions = debugOptions; - impl->transform.jumpTo(camera); + impl->transform->jumpTo(camera); renderStill(std::move(callback)); } @@ -116,35 +119,35 @@ void Map::setStyle(std::unique_ptr