From d54c33d7c30072dd4b61f5ff83e130ea500db1ec Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 22:16:17 +0900 Subject: [PATCH 01/20] Introduce KtLint --- .editorconfig | 4 ++++ build.gradle.kts | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c7797d0e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.{kt,kts}] +indent_style = space +indent_size = 4 +max_line_length = 120 diff --git a/build.gradle.kts b/build.gradle.kts index 2cbb2c0d..2a7a48b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.android.library) apply false + id("org.jlleitschuh.gradle.ktlint") version "13.0.0-rc.1" } buildscript { @@ -14,4 +15,13 @@ buildscript { dependencies { classpath(libs.secrets.gradle.plugin) } -} \ No newline at end of file +} +ktlint { + debug.set(false) + android.set(true) + outputColorName.set("RED") + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } +} From 71458067cff575011e16d1bd4bec895ca737a1fb Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 22:16:56 +0900 Subject: [PATCH 02/20] Fix: KtLint errors --- gradlew | 0 settings.gradle.kts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 gradlew diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/settings.gradle.kts b/settings.gradle.kts index 5a552b22..a34f5aee 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,5 +37,5 @@ include( ":mapconductor-for-mapbox", ":mapconductor-for-googlemaps", ":mapconductor-for-arcgis", - ":mapconductor-core" + ":mapconductor-core", ) From 4f87ac4a211339706db771f4983e5b871369e536 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 22:23:39 +0900 Subject: [PATCH 03/20] fix: Android lint errors --- mapconductor-core/src/main/AndroidManifest.xml | 5 ++--- .../src/main/java/com/mapconductor/core/geocell/KDTree.kt | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mapconductor-core/src/main/AndroidManifest.xml b/mapconductor-core/src/main/AndroidManifest.xml index baecb56a..0d67975c 100644 --- a/mapconductor-core/src/main/AndroidManifest.xml +++ b/mapconductor-core/src/main/AndroidManifest.xml @@ -1,11 +1,10 @@ - + \ No newline at end of file diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt index 28d805e4..39fbbcf9 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt @@ -61,7 +61,7 @@ class KDTree(private val points: List) { val distSq = squaredDistance(query, node.cell.centerXY) if (queue.size < k) { queue.offer(distSq to node.cell) - } else if (distSq < queue.peek().first) { + } else if (distSq < queue.peek()!!.first) { queue.poll() queue.offer(distSq to node.cell) } @@ -71,7 +71,7 @@ class KDTree(private val points: List) { val (near, far) = if (queryVal < nodeVal) node.left to node.right else node.right to node.left nearestK(near, query, k, queue) val axisDist = (queryVal - nodeVal).pow(2) - if (queue.size < k || axisDist < queue.peek().first) { + if (queue.size < k || axisDist < queue.peek()!!.first) { nearestK(far, query, k, queue) } } From 0d6e31a124d2f9438f5a944e0158dd3a2568168b Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 22:27:56 +0900 Subject: [PATCH 04/20] add reviewdog check --- .github/workflows/reviewdog_pr.yml | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/reviewdog_pr.yml diff --git a/.github/workflows/reviewdog_pr.yml b/.github/workflows/reviewdog_pr.yml new file mode 100644 index 00000000..0b0d4e36 --- /dev/null +++ b/.github/workflows/reviewdog_pr.yml @@ -0,0 +1,57 @@ +name: PR Code Review (ktlint + Android Lint) + +on: + pull_request: + branches: [ "**" ] + +jobs: + review: + name: Review with ktlint & Android Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17 + + - name: Grant execute permission to gradlew + run: chmod +x ./gradlew + + - name: Run ktlint (output to file) + run: ./gradlew ktlintCheck > ktlint-report.txt || true + + - name: Run Android Lint + run: ./gradlew lint || true + + - name: ReviewDog - ktlint + uses: reviewdog/action-reviewdog@v5 + with: + name: ktlint + reporter: github-pr-review + level: warning + fail_on_error: false + filter_mode: diff_context + tool_name: ktlint + github_token: ${{ secrets.GITHUB_TOKEN }} + input: ktlint-report.txt + + - name: ReviewDog - Android Lint (multi-module) + run: | + echo "Searching for all lint-results.xml files..." + find . -type f -name "lint-results.xml" | while read lint_file; do + echo "Reporting for: $lint_file" + reviewdog -name="android-lint" \ + -f=checkstyle \ + -f.diff="git diff FETCH_HEAD" \ + -reporter=github-pr-review \ + -level=warning \ + -fail-on-error=false \ + < "$lint_file" + done + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 2c9b8ad61180b9550fa73421edb7c3ec4699d96f Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 22:33:07 +0900 Subject: [PATCH 05/20] Run KtLint and Android lint for every commits --- .github/workflows/code_check.yml | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/code_check.yml diff --git a/.github/workflows/code_check.yml b/.github/workflows/code_check.yml new file mode 100644 index 00000000..aa7937d3 --- /dev/null +++ b/.github/workflows/code_check.yml @@ -0,0 +1,40 @@ +name: Static Analysis on Commit (Selective) + +on: + push: + branches: [ "**" ] + +jobs: + lint: + name: Run Lint if .kt changed + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17 + + - name: Detect changed .kt files + id: changes + run: | + git fetch origin main --depth=1 + CHANGED=$(git diff --name-only HEAD^ | grep '\.kt$' || true) + echo "changed_files=${CHANGED}" >> $GITHUB_OUTPUT + if [ -z "$CHANGED" ]; then + echo "skip=true" >> $GITHUB_OUTPUT + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + + - name: Run ktlint + if: steps.changes.outputs.skip == 'false' + run: ./gradlew ktlintCheck + + - name: Run Android Lint + if: steps.changes.outputs.skip == 'false' + run: ./gradlew lint From 71f3e9dcc24e6e1906a59fa992390e040f37e98c Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 22:48:57 +0900 Subject: [PATCH 06/20] Detect the code change by modules --- .github/workflows/code_check.yml | 92 ++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 21 deletions(-) diff --git a/.github/workflows/code_check.yml b/.github/workflows/code_check.yml index aa7937d3..1abafb86 100644 --- a/.github/workflows/code_check.yml +++ b/.github/workflows/code_check.yml @@ -1,4 +1,4 @@ -name: Static Analysis on Commit (Selective) +name: Android Lint + KtLint by Module (Selective) on: push: @@ -6,35 +6,85 @@ on: jobs: lint: - name: Run Lint if .kt changed runs-on: ubuntu-latest + name: Run Lint and KtLint on Changed Modules steps: - - name: Checkout code - uses: actions/checkout@v3 + - name: Download previous hash cache + uses: actions/download-artifact@v4 + with: + name: lint-hash-cache + path: .lint-hash-cache + if-no-files-found: ignore + + - name: Checkout + uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: 17 - - name: Detect changed .kt files - id: changes + - name: Download previous hash cache + uses: actions/download-artifact@v4 + with: + name: lint-hash-cache + path: .lint-hash-cache + if-no-files-found: ignore + + - name: Define modules + id: define run: | - git fetch origin main --depth=1 - CHANGED=$(git diff --name-only HEAD^ | grep '\.kt$' || true) - echo "changed_files=${CHANGED}" >> $GITHUB_OUTPUT - if [ -z "$CHANGED" ]; then - echo "skip=true" >> $GITHUB_OUTPUT - else - echo "skip=false" >> $GITHUB_OUTPUT - fi - - - name: Run ktlint - if: steps.changes.outputs.skip == 'false' - run: ./gradlew ktlintCheck + echo "modules=app feature/login core/common" >> $GITHUB_OUTPUT + + - name: Detect changed modules + id: detect + run: | + changed_modules="" + mkdir -p .lint-hash-cache + + for module in ${{ steps.define.outputs.modules }}; do + key="lint-hash-${module//\//-}-${{ runner.os }}" + hash=$(find "$module/src" -type f \( -name "*.kt" -o -name "*.xml" \) | sort | xargs sha256sum | sha256sum | cut -d ' ' -f1) + + echo "Current hash for $module: $hash" + + CACHE_KEY_FILE=".lint-hash-cache/${module//\//-}.txt" + + if [ -f "$CACHE_KEY_FILE" ]; then + old_hash=$(cat "$CACHE_KEY_FILE") + if [ "$hash" == "$old_hash" ]; then + echo "No change in $module, skipping." + continue + fi + fi + + echo "Module changed: $module" + changed_modules="$changed_modules $module" + echo "$hash" > "$CACHE_KEY_FILE" + done + + echo "changed_modules=${changed_modules}" >> $GITHUB_OUTPUT - name: Run Android Lint - if: steps.changes.outputs.skip == 'false' - run: ./gradlew lint + if: steps.detect.outputs.changed_modules != '' + run: | + for module in ${{ steps.detect.outputs.changed_modules }}; do + echo "Running Android Lint for $module" + ./gradlew ":$module:lint" + done + + - name: Run ktlintCheck + if: steps.detect.outputs.changed_modules != '' + run: | + for module in ${{ steps.detect.outputs.changed_modules }}; do + echo "Running ktlintCheck for $module" + ./gradlew ":$module:ktlintCheck" + done + + - name: Upload hash cache + uses: actions/upload-artifact@v4 + with: + name: lint-hash-cache + path: .lint-hash-cache/ From 4fc9fc58bc9d5c208c5536c41284b9754cdb0ec5 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 23:01:30 +0900 Subject: [PATCH 07/20] continue steps if no cache --- .github/workflows/code_check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/code_check.yml b/.github/workflows/code_check.yml index 1abafb86..fd84c8cc 100644 --- a/.github/workflows/code_check.yml +++ b/.github/workflows/code_check.yml @@ -16,6 +16,7 @@ jobs: name: lint-hash-cache path: .lint-hash-cache if-no-files-found: ignore + continue-on-error: true - name: Checkout uses: actions/checkout@v4 From 76f0b1b52f02a6bcc66e6db7f8a33512d3c8ba58 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 23:03:07 +0900 Subject: [PATCH 08/20] fix: duplicate steps are defined in the code_check.yml --- .github/workflows/code_check.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/code_check.yml b/.github/workflows/code_check.yml index fd84c8cc..9c79b35a 100644 --- a/.github/workflows/code_check.yml +++ b/.github/workflows/code_check.yml @@ -10,13 +10,6 @@ jobs: name: Run Lint and KtLint on Changed Modules steps: - - name: Download previous hash cache - uses: actions/download-artifact@v4 - with: - name: lint-hash-cache - path: .lint-hash-cache - if-no-files-found: ignore - continue-on-error: true - name: Checkout uses: actions/checkout@v4 @@ -33,6 +26,7 @@ jobs: name: lint-hash-cache path: .lint-hash-cache if-no-files-found: ignore + continue-on-error: true - name: Define modules id: define From 00edc4b0fbbc37c96df046fbd60eca84b63be595 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 23:24:45 +0900 Subject: [PATCH 09/20] merge code_check.yml and reviewdog_pr.yml into ci.yml --- .github/workflows/{code_check.yml => ci.yml} | 58 +++++++++++--------- .github/workflows/reviewdog_pr.yml | 57 ------------------- project.properties | 0 3 files changed, 33 insertions(+), 82 deletions(-) rename .github/workflows/{code_check.yml => ci.yml} (58%) delete mode 100644 .github/workflows/reviewdog_pr.yml create mode 100644 project.properties diff --git a/.github/workflows/code_check.yml b/.github/workflows/ci.yml similarity index 58% rename from .github/workflows/code_check.yml rename to .github/workflows/ci.yml index 9c79b35a..ffc7979d 100644 --- a/.github/workflows/code_check.yml +++ b/.github/workflows/ci.yml @@ -1,82 +1,90 @@ -name: Android Lint + KtLint by Module (Selective) +name: Android Code Check (Lint + KtLint + ReviewDog) on: push: branches: [ "**" ] + pull_request: + branches: [ "**" ] jobs: - lint: + code-check: runs-on: ubuntu-latest - name: Run Lint and KtLint on Changed Modules + name: Lint & ReviewDog on Changed Modules steps: - - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Set up Java - uses: actions/setup-java@v4 + uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: 17 - - name: Download previous hash cache + - name: Download hash cache uses: actions/download-artifact@v4 with: name: lint-hash-cache path: .lint-hash-cache - if-no-files-found: ignore continue-on-error: true - - name: Define modules + - name: Read module list from projects.properties id: define run: | - echo "modules=app feature/login core/common" >> $GITHUB_OUTPUT + modules=$(grep '^modules=' projects.properties | cut -d= -f2) + echo "modules=$modules" >> $GITHUB_OUTPUT - - name: Detect changed modules + - name: Detect changed modules by hashing src/ id: detect run: | changed_modules="" mkdir -p .lint-hash-cache - for module in ${{ steps.define.outputs.modules }}; do - key="lint-hash-${module//\//-}-${{ runner.os }}" hash=$(find "$module/src" -type f \( -name "*.kt" -o -name "*.xml" \) | sort | xargs sha256sum | sha256sum | cut -d ' ' -f1) - - echo "Current hash for $module: $hash" - CACHE_KEY_FILE=".lint-hash-cache/${module//\//-}.txt" - if [ -f "$CACHE_KEY_FILE" ]; then old_hash=$(cat "$CACHE_KEY_FILE") if [ "$hash" == "$old_hash" ]; then - echo "No change in $module, skipping." continue fi fi - echo "Module changed: $module" changed_modules="$changed_modules $module" echo "$hash" > "$CACHE_KEY_FILE" done - echo "changed_modules=${changed_modules}" >> $GITHUB_OUTPUT + - name: Run ktlintCheck + if: steps.detect.outputs.changed_modules != '' + run: | + for module in ${{ steps.detect.outputs.changed_modules }}; do + ./gradlew ":$module:ktlintCheck" + done + - name: Run Android Lint if: steps.detect.outputs.changed_modules != '' run: | for module in ${{ steps.detect.outputs.changed_modules }}; do - echo "Running Android Lint for $module" ./gradlew ":$module:lint" done - - name: Run ktlintCheck - if: steps.detect.outputs.changed_modules != '' + - name: Run ReviewDog (only on pull_request) + if: github.event_name == 'pull_request' && steps.detect.outputs.changed_modules != '' run: | for module in ${{ steps.detect.outputs.changed_modules }}; do - echo "Running ktlintCheck for $module" - ./gradlew ":$module:ktlintCheck" + lint_file=$(find "$module" -name "lint-results.xml" | head -n 1) + if [ -f "$lint_file" ]; then + echo "Reporting Lint results for $module" + reviewdog -name="android-lint ($module)" \ + -f=checkstyle \ + -f.diff="git diff origin/${{ github.base_ref }}" \ + -reporter=github-pr-review \ + -level=warning \ + < "$lint_file" + fi done + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload hash cache uses: actions/upload-artifact@v4 diff --git a/.github/workflows/reviewdog_pr.yml b/.github/workflows/reviewdog_pr.yml deleted file mode 100644 index 0b0d4e36..00000000 --- a/.github/workflows/reviewdog_pr.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: PR Code Review (ktlint + Android Lint) - -on: - pull_request: - branches: [ "**" ] - -jobs: - review: - name: Review with ktlint & Android Lint - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Java - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: 17 - - - name: Grant execute permission to gradlew - run: chmod +x ./gradlew - - - name: Run ktlint (output to file) - run: ./gradlew ktlintCheck > ktlint-report.txt || true - - - name: Run Android Lint - run: ./gradlew lint || true - - - name: ReviewDog - ktlint - uses: reviewdog/action-reviewdog@v5 - with: - name: ktlint - reporter: github-pr-review - level: warning - fail_on_error: false - filter_mode: diff_context - tool_name: ktlint - github_token: ${{ secrets.GITHUB_TOKEN }} - input: ktlint-report.txt - - - name: ReviewDog - Android Lint (multi-module) - run: | - echo "Searching for all lint-results.xml files..." - find . -type f -name "lint-results.xml" | while read lint_file; do - echo "Reporting for: $lint_file" - reviewdog -name="android-lint" \ - -f=checkstyle \ - -f.diff="git diff FETCH_HEAD" \ - -reporter=github-pr-review \ - -level=warning \ - -fail-on-error=false \ - < "$lint_file" - done - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/project.properties b/project.properties new file mode 100644 index 00000000..e69de29b From 839a16a0565f2a5c3dcaeeba901ceb91e478137c Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 23:27:48 +0900 Subject: [PATCH 10/20] Add necessary files --- project.properties | 1 + settings.gradle.kts | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/project.properties b/project.properties index e69de29b..2caac8a1 100644 --- a/project.properties +++ b/project.properties @@ -0,0 +1 @@ +modules=example-app,mapconductor-for-here,mapconductor-for-mapbox,mapconductor-for-googlemaps,mapconductor-for-arcgis,mapconductor-core diff --git a/settings.gradle.kts b/settings.gradle.kts index a34f5aee..128a46d2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,11 +31,19 @@ dependencyResolutionManagement { } rootProject.name = "MapConductorSDK" -include( - ":example-app", - ":mapconductor-for-here", - ":mapconductor-for-mapbox", - ":mapconductor-for-googlemaps", - ":mapconductor-for-arcgis", - ":mapconductor-core", -) +//include( +// ":example-app", +// ":mapconductor-for-here", +// ":mapconductor-for-mapbox", +// ":mapconductor-for-googlemaps", +// ":mapconductor-for-arcgis", +// ":mapconductor-core", +//) +val modulesProp = rootDir.resolve("projects.properties").readLines() + .firstOrNull { it.startsWith("modules=") } + ?.removePrefix("modules=") + ?.split(",") + ?.map { it.trim() } + ?: emptyList() + +modulesProp.forEach { include(":$it") } From 7fd43e0be27495dc9801a173343c0175568722c8 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 23:32:16 +0900 Subject: [PATCH 11/20] Fix: the ci workflow can not find the projects.properties file --- .github/workflows/ci.yml | 4 ++-- project.properties => projects.properties | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename project.properties => projects.properties (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffc7979d..c6a536cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: 17 diff --git a/project.properties b/projects.properties similarity index 100% rename from project.properties rename to projects.properties From 342b2e6e6471b33dc6ee4cb05257ff55e1203e52 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Tue, 3 Jun 2025 23:41:38 +0900 Subject: [PATCH 12/20] fix: KtLint does not work --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6a536cb..2c856e5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - name: Read module list from projects.properties id: define run: | - modules=$(grep '^modules=' projects.properties | cut -d= -f2) + modules=$(grep '^modules=' projects.properties | cut -d= -f2 | tr ',' ' ') echo "modules=$modules" >> $GITHUB_OUTPUT - name: Detect changed modules by hashing src/ From a05d138b6e736c53ceeda2959529e600141308f4 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 09:59:22 +0900 Subject: [PATCH 13/20] fix: KtLint and Android Lint errors --- .editorconfig | 31 + build.gradle.kts | 9 - example-app/build.gradle.kts | 15 +- .../example/ExampleInstrumentedTest.kt | 8 +- .../main/java/com/mapconductor/DemoData.kt | 1883 +++++++++-------- .../com/mapconductor/example/AppViewModel.kt | 48 +- .../com/mapconductor/example/DemoAppScreen.kt | 153 +- .../com/mapconductor/example/MainActivity.kt | 20 +- .../java/com/mapconductor/example/MapArea.kt | 22 +- .../java/com/mapconductor/example/MapView.kt | 35 +- .../mapconductor/example/demo/StoreCard.kt | 50 +- .../com/mapconductor/example/demo/TableRow.kt | 15 +- .../com/mapconductor/example/toast/Toast.kt | 31 +- .../example/toast/ToastMessage.kt | 2 +- .../mapconductor/example/ui/IconSelectMenu.kt | 40 +- .../mapconductor/example/ui/theme/Color.kt | 2 +- .../mapconductor/example/ui/theme/Theme.kt | 33 +- .../com/mapconductor/example/ui/theme/Type.kt | 22 +- .../mapconductor/example/ExampleUnitTest.kt | 5 +- mapconductor-core/build.gradle.kts | 12 +- .../core/ExampleInstrumentedTest.kt | 8 +- .../com/mapconductor/core/InitProvider.kt | 11 +- .../core/MapCameraPositionBase.kt | 36 - .../mapconductor/core/MapOverlayRegistry.kt | 20 - .../com/mapconductor/core/MapPaddingsImpl.kt | 29 - .../com/mapconductor/core/MarkerManager.kt | 589 +++--- .../java/com/mapconductor/core/MathExt.kt | 5 +- .../main/java/com/mapconductor/core/Offset.kt | 2 +- .../com/mapconductor/core/OverlayProvider.kt | 58 +- .../com/mapconductor/core/ResourceProvider.kt | 45 +- .../mapconductor/core/{utils.kt => Utils.kt} | 1 - .../core/controller/BaseMapViewController.kt | 97 +- .../mapconductor/core/features/GeoPoint.kt | 41 +- .../core/features/GeoRectBounds.kt | 49 +- .../core/geocell/HexCellRegistry.kt | 48 +- .../mapconductor/core/geocell/HexGeocell.kt | 113 +- .../com/mapconductor/core/geocell/KDTree.kt | 69 +- .../mapconductor/core/info/DrawInfoBubble.kt | 130 +- .../core/info/InfoBubbleCompose.kt | 3 +- .../core/info/InfoWindowOverlay.kt | 29 +- .../core/map/MapCameraPositionBase.kt | 36 + .../{MapStyle.kt => map/MapDesignType.kt} | 5 +- .../mapconductor/core/map/MapPaddingsImpl.kt | 33 + .../com/mapconductor/core/map/MapViewBase.kt | 59 +- .../core/{ => map}/MapViewHolder.kt | 4 +- .../core/{ => map}/MapViewHolderStore.kt | 22 +- .../com/mapconductor/core/map/MapViewScope.kt | 6 +- .../com/mapconductor/core/map/MapViewState.kt | 48 +- .../com/mapconductor/core/marker/Marker.kt | 3 +- .../mapconductor/core/marker/MarkerCompose.kt | 49 +- .../com/mapconductor/core/marker/MarkerDsl.kt | 37 +- .../core/marker/MarkerIconProp.kt | 18 +- .../mapconductor/core/marker/MarkerProps.kt | 17 +- .../core/projection/Projection.kt | 5 +- .../com/mapconductor/core/projection/WGS84.kt | 8 +- .../core/projection/WebMercator.kt | 5 +- ...aversinDistance.kt => HaversinDistance.kt} | 12 +- .../mapconductor/core/state/StaticOrValue.kt | 36 +- .../com/mapconductor/core/ExampleUnitTest.kt | 5 +- mapconductor-for-arcgis/build.gradle.kts | 13 +- .../googlemaps/ExampleInstrumentedTest.kt | 8 +- .../arcgis/ArcGISMapDesignType.kt | 149 +- .../com/mapconductor/arcgis/ArcGISMapView.kt | 82 +- .../arcgis/ArcGISMapViewController.kt | 172 +- .../arcgis/ArcGISMapViewHolderImpl.kt | 33 +- .../arcgis/ArcGISMapViewHolderStore.kt | 17 +- .../arcgis/ArcGISMapViewInitOptions.kt | 2 +- .../mapconductor/arcgis/ArcGISMapViewScope.kt | 2 +- .../mapconductor/arcgis/ArcGisMapViewState.kt | 160 +- .../java/com/mapconductor/arcgis/GeoPoint.kt | 42 +- .../arcgis/IArcGISMapEventHandler.kt | 4 +- .../mapconductor/arcgis/MapCameraPosition.kt | 194 +- .../googlemaps/ExampleUnitTest.kt | 5 +- mapconductor-for-googlemaps/build.gradle.kts | 13 +- .../googlemaps/ExampleInstrumentedTest.kt | 8 +- .../com/mapconductor/googlemaps/GeoPoint.kt | 4 +- .../googlemaps/GoogleMapDesign.kt | 27 +- .../googlemaps/GoogleMapExtension.kt | 5 - .../mapconductor/googlemaps/GoogleMapView.kt | 77 +- .../googlemaps/GoogleMapViewController.kt | 121 +- .../googlemaps/GoogleMapViewHolderImpl.kt | 9 +- .../googlemaps/GoogleMapViewHolderStore.kt | 21 +- .../googlemaps/GoogleMapViewScope.kt | 2 +- .../googlemaps/GoogleMapViewState.kt | 163 +- .../googlemaps/IGoogleMapEventHandler.kt | 6 +- .../googlemaps/MapCameraPosition.kt | 45 +- .../googlemaps/ExampleUnitTest.kt | 5 +- mapconductor-for-here/build.gradle.kts | 13 +- .../here/ExampleInstrumentedTest.kt | 8 +- .../java/com/mapconductor/here/BitmapIcon.kt | 10 +- .../java/com/mapconductor/here/GeoPoint.kt | 11 +- .../mapconductor/here/HereMapController.kt | 144 +- .../com/mapconductor/here/HereMapDesign.kt | 90 +- .../com/mapconductor/here/HereMapExtension.kt | 2 - .../java/com/mapconductor/here/HereMapView.kt | 91 +- .../here/HereMapViewHolderImpl.kt | 28 +- .../here/HereMapViewHolderStore.kt | 54 +- .../here/HereMapViewInitOptions.kt | 2 +- .../com/mapconductor/here/HereMapViewScope.kt | 3 +- .../com/mapconductor/here/HereMapViewState.kt | 146 +- .../mapconductor/here/IHereMapEventHandler.kt | 4 +- .../mapconductor/here/MapCameraPosition.kt | 100 +- .../com/mapconductor/here/ExampleUnitTest.kt | 5 +- mapconductor-for-mapbox/build.gradle.kts | 13 +- .../mapbox/ExampleInstrumentedTest.kt | 8 +- .../java/com/mapconductor/mapbox/GeoPoint.kt | 4 +- .../mapbox/IMapboxMapEventHandler.kt | 4 +- .../mapconductor/mapbox/MapCameraPosition.kt | 80 +- .../mapconductor/mapbox/MapboxExtension.kt | 5 +- .../mapconductor/mapbox/MapboxMapDesign.kt | 48 +- .../com/mapconductor/mapbox/MapboxMapView.kt | 51 +- .../mapbox/MapboxMapViewController.kt | 163 +- .../mapbox/MapboxMapViewHolderImpl.kt | 14 +- .../mapbox/MapboxMapViewHolderStore.kt | 20 +- .../mapconductor/mapbox/MapboxMapViewScope.kt | 5 +- .../com/mapconductor/mapbox/MapboxPaddings.kt | 38 +- .../mapconductor/mapbox/MapboxViewState.kt | 141 +- .../mapconductor/mapbox/ExampleUnitTest.kt | 5 +- settings.gradle.kts | 24 +- 119 files changed, 3794 insertions(+), 3156 deletions(-) delete mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/MapCameraPositionBase.kt delete mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/MapOverlayRegistry.kt delete mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/MapPaddingsImpl.kt rename mapconductor-core/src/main/java/com/mapconductor/core/{utils.kt => Utils.kt} (99%) create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/map/MapCameraPositionBase.kt rename mapconductor-core/src/main/java/com/mapconductor/core/{MapStyle.kt => map/MapDesignType.kt} (64%) create mode 100644 mapconductor-core/src/main/java/com/mapconductor/core/map/MapPaddingsImpl.kt rename mapconductor-core/src/main/java/com/mapconductor/core/{ => map}/MapViewHolder.kt (72%) rename mapconductor-core/src/main/java/com/mapconductor/core/{ => map}/MapViewHolderStore.kt (67%) rename mapconductor-core/src/main/java/com/mapconductor/core/spherical/{haversinDistance.kt => HaversinDistance.kt} (72%) delete mode 100644 mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapExtension.kt delete mode 100644 mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapExtension.kt diff --git a/.editorconfig b/.editorconfig index c7797d0e..dd5f1801 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,35 @@ +# Top-level EditorConfig configuration file +root = true + +# Global defaults +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Kotlin-specific rules [*.{kt,kts}] indent_style = space indent_size = 4 +continuation_indent_size = 4 max_line_length = 120 +insert_final_newline = true +ktlint_standard_argument-list-wrapping = disabled +ktlint_standard_function-naming = disabled + +# Disable wildcard imports +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,android.** +ij_kotlin_allow_wildcard_imports = false + +# Sort imports alphabetically and by groups +ij_kotlin_optimize_imports = true + +# Do not align multiline parameters or arguments +ij_formatter_align_multiline_parameters = false +ij_formatter_align_multiline_method_brackets = false + +# XML files (layout, manifest, etc.) +[*.xml] +indent_style = space +indent_size = 4 diff --git a/build.gradle.kts b/build.gradle.kts index 2a7a48b2..06ace7da 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,12 +16,3 @@ buildscript { classpath(libs.secrets.gradle.plugin) } } -ktlint { - debug.set(false) - android.set(true) - outputColorName.set("RED") - reporters { - reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) - reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) - } -} diff --git a/example-app/build.gradle.kts b/example-app/build.gradle.kts index ab9449f4..6bb361f5 100644 --- a/example-app/build.gradle.kts +++ b/example-app/build.gradle.kts @@ -4,8 +4,16 @@ plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin") + id("org.jlleitschuh.gradle.ktlint") } +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } +} android { namespace = "com.mapconductor.example" @@ -14,7 +22,7 @@ android { defaultConfig { applicationId = "com.mapconductor.example" minSdk = project.property("minSdk").toString().toInt() - targetSdk = project.property("targetSdk").toString().toInt() + targetSdk = project.property("targetSdk").toString().toInt() versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -30,7 +38,7 @@ android { isMinifyEnabled = project.property("isMinifyEnabled").toString().toBoolean() proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } @@ -75,7 +83,6 @@ dependencies { implementation(libs.androidx.material3) implementation(libs.androidx.appcompat) - // Google Maps SDK implementation(libs.play.services.maps) // Here Maps SDK @@ -108,4 +115,4 @@ dependencies { androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) -} \ No newline at end of file +} diff --git a/example-app/src/androidTest/java/com/mapconductor/example/ExampleInstrumentedTest.kt b/example-app/src/androidTest/java/com/mapconductor/example/ExampleInstrumentedTest.kt index 4e999eee..8880290f 100644 --- a/example-app/src/androidTest/java/com/mapconductor/example/ExampleInstrumentedTest.kt +++ b/example-app/src/androidTest/java/com/mapconductor/example/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.mapconductor.example -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.mapconductor.overlaytest", appContext.packageName) } -} \ No newline at end of file +} diff --git a/example-app/src/main/java/com/mapconductor/DemoData.kt b/example-app/src/main/java/com/mapconductor/DemoData.kt index 77adf47f..08115cef 100644 --- a/example-app/src/main/java/com/mapconductor/DemoData.kt +++ b/example-app/src/main/java/com/mapconductor/DemoData.kt @@ -1,884 +1,1019 @@ package com.mapconductor -import android.os.Bundle import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.marker.MarkerState +import android.os.Bundle /** * This example uses publicly available business addresses (e.g., Starbucks) and geocodes them * using the U.S. Census Bureau Geocoding API. * No personally identifiable information (PII) is used or inferred. */ -val StarbucksHI_list = listOf( - MarkerState( - position = GeoPoint( - latitude = 21.647441446388, - longitude = -158.062544988096, - ), - extra = Bundle().apply { - putString("name", "Pupukea (North Shore)") - putString("address", "59-720 Kamehameha Highway, Haleiwa, HI 96712") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - }, - ), - MarkerState( - position = GeoPoint( - latitude = 21.33310051533, - longitude = -157.922371535818, - ), - extra = Bundle().apply { - putString("name", "Honolulu Airport (HNL) – Main") - putString("address", "300 Rogers Blvd, Honolulu, HI 96820") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.378981027427, - longitude = -157.930536387573, - ), - extra = Bundle().apply { - putString("name", "Aiea Shopping Center") - putString("address", "99-115 Aiea Heights Drive #125, Aiea, HI 96701") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.38441101519, - longitude = -157.944839558127, - ), - extra = Bundle().apply { - putString("name", "Pearlridge Center") - putString("address", "98-125 Kaonohi Street, Aiea, HI 96701") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.363785189939, - longitude = -157.928412704343, - ), - extra = Bundle().apply { - putString("name", "Stadium Marketplace") - putString("address", "4561 Salt Lake Boulevard, Aiea, HI 96818") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.386340299119, - longitude = -157.941897795274, - ), - extra = Bundle().apply { - putString("name", "Pearlridge Mall") - putString("address", "98-1005 Moanalua Road, Aiea, HI 96701") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 19.69971686484, - longitude = -155.067322812851, - ), - extra = Bundle().apply { - putString("name", "Waiakea Center (Hilo)") - putString("address", "315-325 Makaala Street, Hilo, HI 96720") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 19.695097953188, - longitude = -155.06690203818, - ), - extra = Bundle().apply { - putString("name", "Prince Kuhio Plaza (Hilo)") - putString("address", "111 East Puainako Street, Hilo, HI 96720") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 19.719877684807, - longitude = -155.082770375139, - ), - extra = Bundle().apply { - putString("name", "Downtown Hilo (Kilauea Ave)") - putString("address", "438 Kilauea Ave, Hilo, HI 96720") - putBoolean("instore", true) - putBoolean("drive_through", true) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.33593, - longitude = -157.91581, - ), - extra = Bundle().apply { - putString("name", "Airport Trade Center") - putString("address", "Airport Trade Center, 550 Paiea St, Honolulu, HI 96819") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.307358712377, - longitude = -157.865194116049, - ), - extra = Bundle().apply { - putString("name", "Aloha Tower") - putString("address", "1 Aloha Tower Drive, Honolulu, HI 96813") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.30846253, - longitude = -157.8614898, - ), - extra = Bundle().apply { - putString("name", "Bishop (Downtown)") - putString("address", "1000 Bishop Street #104, Honolulu, HI 96813") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.307604966533, - longitude = -157.860743724617, - ), - extra = Bundle().apply { - putString("name", "Pickup – King & Alakea") - putString("address", "220 South King Street, Honolulu, HI 96813") - putBoolean("instore", false) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.285300825278, - longitude = -157.83841421971, - ), - extra = Bundle().apply { - putString("name", "Discovery Bay Center") - putString("address", "1778 Ala Moana Boulevard, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.334058693598, - longitude = -158.023228524098, - ), - extra = Bundle().apply { - putString("name", "Ewa Beach – Laulani Village") - putString("address", "91-1401 Fort Weaver Road, Ewa Beach, HI 96706") - putBoolean("instore", true) - putBoolean("drive_through", true) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.280578442859, - longitude = -157.828071689214, - ), - extra = Bundle().apply { - putString("name", "DFS (Duty Free) Waikiki") - putString("address", "330 Royal Hawaiian Avenue, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.308557010703, - longitude = -157.862582769768, - ), - extra = Bundle().apply { - putString("name", "Financial Plaza (Downtown)") - putString("address", "130 Merchant Street #111, Honolulu, HI 96813") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.282048, - longitude = -157.713041, - ), - extra = Bundle().apply { - putString("name", "Hawaii Kai Town Center") - putString("address", "6700 Kalanianaole Highway, Honolulu, HI 96825") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.291792650634, - longitude = -157.849735879475, - ), - extra = Bundle().apply { - putString("name", "Hokua (Ala Moana)") - putString("address", "1288 Ala Moana Blvd, Honolulu, HI 96814") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.335246981366, - longitude = -157.868748238078, - ), - extra = Bundle().apply { - putString("name", "Kamehameha Shopping Center") - putString("address", "1620 North School Street, Honolulu, HI 96817") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.27852422, - longitude = -157.7875773, - ), - extra = Bundle().apply { - putString("name", "Kahala Mall") - putString("address", "4211 Waialae Avenue, Honolulu, HI 96816") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.294505444307, - longitude = -157.841946089363, - ), - extra = Bundle().apply { - putString("name", "Keeaumoku (WalMart)") - putString("address", "678 Keeaumoku Street #106, Honolulu, HI 96814") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.279056707748, - longitude = -157.813890137018, - ), - extra = Bundle().apply { - putString("name", "Kapahulu Avenue") - putString("address", "625 Kapahulu Avenue, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.276148191143, - longitude = -157.704922547261, - ), - extra = Bundle().apply { - putString("name", "Koko Marina Center") - putString("address", "7192 Kalanianaole Highway, Honolulu, HI 96825") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.30985278855, - longitude = -157.810260198584, - ), - extra = Bundle().apply { - putString("name", "Manoa Valley") - putString("address", "2902 East Manoa Road, Honolulu, HI 96822") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.289750395336, - longitude = -157.843910788044, - ), - extra = Bundle().apply { - putString("name", "Macy’s Ala Moana Center") - putString("address", "1450 Ala Moana Boulevard, Honolulu, HI 96814") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.341260775481, - longitude = -157.929507250967, - ), - extra = Bundle().apply { - putString("name", "Moanalua Shopping Center") - putString("address", "930 Valkenburgh Street, Honolulu, HI 96818") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.278908517307, - longitude = -157.832413265507, - ), - extra = Bundle().apply { - putString("name", "Outrigger Reef (Waikiki)") - putString("address", "2169 Kalia Road #102, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.279011840151, - longitude = -157.825557564916, - ), - extra = Bundle().apply { - putString("name", "Ohana Waikiki West") - putString("address", "2330 Kuhio Avenue, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.30278387167, - longitude = -157.879450611886, - ), - extra = Bundle().apply { - putString("name", "Sand Island") - putString("address", "120 Sand Island Access Road #4, Honolulu, HI 96819") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.289750395336, - longitude = -157.843910788044, - ), - extra = Bundle().apply { - putString("name", "Sears Ala Moana Center") - putString("address", "1450 Ala Moana Blvd, Honolulu, HI 96814") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.2786604527, - longitude = -157.828371626919, - ), - extra = Bundle().apply { - putString("name", "Waikiki Shopping Plaza") - putString("address", "2270 Kalakaua Avenue #1800, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.280533970958, - longitude = -157.82749796628, - ), - extra = Bundle().apply { - putString("name", "Waikiki Trade Center (Reserve Bar)") - putString("address", "2255 Kuhio Avenue #S-1, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", true) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.293774306726, - longitude = -157.85297798269, - ), - extra = Bundle().apply { - putString("name", "Ward Entertainment Center") - putString("address", "310 Kamakee Street #6, Honolulu, HI 96814") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.406095, - longitude = -157.800761, - ), - extra = Bundle().apply { - putString("name", "Windward City Shopping Center") - putString("address", "45-480 Kaneohe Bay Drive, Kaneohe, HI 96744") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.279515520356, - longitude = -157.829265712704, - ), - extra = Bundle().apply { - putString("name", "Waikiki Walk") - putString("address", "2222 Kalakaua Avenue, Honolulu, HI 96815") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.293340364607, - longitude = -157.85256477721, - ), - extra = Bundle().apply { - putString("name", "Ward Gateway") - putString("address", "1142 Auahi Street, Honolulu, HI 96814") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.888659282451, - longitude = -156.477197459052, - ), - extra = Bundle().apply { - putString("name", "Queen Kaahumanu Center") - putString("address", "275 West Kaahumanu Avenue #1200, Kahului, HI 96732") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.881960703032, - longitude = -156.45511618549, - ), - extra = Bundle().apply { - putString("name", "Maui Marketplace") - putString("address", "270 Dairy Road, Kahului, HI 96732") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.393471214679, - longitude = -157.740438744365, - ), - extra = Bundle().apply { - putString("name", "Kailua Village") - putString("address", "539 Kailua Road, Kailua, HI 96734") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 19.65018280057, - longitude = -155.987752998108, - ), - extra = Bundle().apply { - putString("name", "Kona Coast Shopping Center") - putString("address", "74-5588 Palani Road, Kailua-Kona, HI 96740") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.020593379111, - longitude = -155.668585540658, - ), - extra = Bundle().apply { - putString("name", "Parker Ranch Center") - putString("address", "67-1185 Mamalahoa Highway #D108, Kamuela, HI 96743") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.344027896932, - longitude = -158.11830127628, - ), - extra = Bundle().apply { - putString("name", "Halekuai Center") - putString("address", "563 Farrington Highway #101, Kapolei, HI 96707") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.328579072139, - longitude = -158.086506230214, - ), - extra = Bundle().apply { - putString("name", "Kapolei Parkway & Kamokila") - putString("address", "338 Kamokila Boulevard #108, Kapolei, HI 96797") - putBoolean("instore", true) - putBoolean("drive_through", true) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.734513943855, - longitude = -156.452970465534, - ), - extra = Bundle().apply { - putString("name", "Kukui Mall") - putString("address", "1819 South Kihei Road, Kihei, HI 96738") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.750703062197, - longitude = -156.451408824978, - ), - extra = Bundle().apply { - putString("name", "Piilani Village Shopping Center") - putString("address", "247 Piikea Avenue #106, Kihei, HI 96753") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 19.94001271876, - longitude = -155.856842731652, - ), - extra = Bundle().apply { - putString("name", "Mauna Lani (Kohala Coast)") - putString("address", "68-1330 Mauna Lani Drive #H-101B, Kohala Coast, HI 96743") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.886244, - longitude = -156.684697, - ), - extra = Bundle().apply { - putString("name", "Lahaina Cannery Mall") - putString("address", "1221 Honoapiilani Highway, Lahaina, HI 96761") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.877708110758, - longitude = -156.679031878844, - ), - extra = Bundle().apply { - putString("name", "Lahaina (Front Street)") - putString("address", "845 Wainee Street, Lahaina, HI 96761") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.969553724378, - longitude = -159.388283368972, - ), - extra = Bundle().apply { - putString("name", "Kukui Grove Center") - putString("address", "3-2600 Kaumualii Highway #A8, Lihue, HI 96766") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.889156401906, - longitude = -156.449318101378, - ), - extra = Bundle().apply { - putString("name", "Kahului Airport (OGG)") - putString("address", "1 Keolani Place, Kahului, HI 96732") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.458431746129, - longitude = -158.015862355331, - ), - extra = Bundle().apply { - putString("name", "Mililani Shopping Center") - putString("address", "95-221 Kipapa Drive, Mililani, HI 96789") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.453888574294, - longitude = -158.007690940987, - ), - extra = Bundle().apply { - putString("name", "Mililani Town Center") - putString("address", "95-1249 Meheula Parkway, Mililani, HI 96789") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 20.838230357792, - longitude = -156.342698446307, - ), - extra = Bundle().apply { - putString("name", "Pukalani Foodland Center") - putString("address", "55 Pukalani Street, Pukalani, HI 96768") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.378675, - longitude = -157.728499, - ), - extra = Bundle().apply { - putString("name", "Enchanted Lake Center (Kailua)") - putString("address", "1020 Keolu Drive, Kailua, HI 96734") - putBoolean("instore", true) - putBoolean("drive_through", true) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.389003512015, - longitude = -158.033431400538, - ), - extra = Bundle().apply { - putString("name", "Kunia Shopping Center") - putString("address", "94-673 Kupuohi Street, Waipahu, HI 96797") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.401138284746, - longitude = -158.010288643364, - ), - extra = Bundle().apply { - putString("name", "Waikele Center") - putString("address", "94-799 Lumiaina Street, Waipahu, HI 96797") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.881002766882, - longitude = -159.457726341723, - ), - extra = Bundle().apply { - putString("name", "Poipu Shopping Village") - putString("address", "2360 Kiahuna Plantation Drive #E70, Koloa, HI 96756") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.966834230955, - longitude = -159.381526209527, - ), - extra = Bundle().apply { - putString("name", "Safeway Lihue") - putString("address", "4454 Nuhou Street, Lihue, HI 96766") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.970935289068, - longitude = -159.375643172372, - ), - extra = Bundle().apply { - putString("name", "Target Lihue (Kauai)") - putString("address", "4303 Nawiliwili Road, Lihue, HI 96766") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 22.061786387888, - longitude = -159.320539848567, - ), - extra = Bundle().apply { - putString("name", "Kauai Village SC (Kapaa)") - putString("address", "4-831 Kuhio Highway #208, Kapaa, HI 96746") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.32874860701, - longitude = -158.091318912219, - ), - extra = Bundle().apply { - putString("name", "Target Kapolei") - putString("address", "4450 Kapolei Parkway, Kapolei, HI 96707") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.340541119127, - longitude = -158.124703887408, - ), - extra = Bundle().apply { - putString("name", "Ko Olina Station") - putString("address", "92-1047 Olani Street, Kapolei, HI 96707") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.328151560875, - longitude = -158.021804173199, - ), - extra = Bundle().apply { - putString("name", "Laulani Village (Ewa Beach)") - putString("address", "91-1105 Keaunui Drive #500, Ewa Beach, HI 96706") - putBoolean("instore", true) - putBoolean("drive_through", true) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.34213, - longitude = -157.95157, - ), - extra = Bundle().apply { - putString("name", "Hickam AFB (Base Access)") - putString("address", "Bldg B-1250, Hickam AFB, Honolulu, HI 96853") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.349070820935, - longitude = -157.932730132699, - ), - extra = Bundle().apply { - putString("name", "Pearl Harbor NEX") - putString("address", "4725 Bougainville Drive, Honolulu, HI 96818") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), - MarkerState( - position = GeoPoint( - latitude = 21.356011085979, - longitude = -157.893896231076, - ), - extra = Bundle().apply { - putString("name", "Tripler Army Medical Center") - putString("address", "1 Jarrett White Road, Honolulu, HI 96859") - putBoolean("instore", true) - putBoolean("drive_through", false) - putBoolean("only_reserved", false) - } - ), -) \ No newline at end of file +val StarbucksHI_list = + listOf( + MarkerState( + position = + GeoPoint( + latitude = 21.647441446388, + longitude = -158.062544988096, + ), + extra = + Bundle().apply { + putString("name", "Pupukea (North Shore)") + putString("address", "59-720 Kamehameha Highway, Haleiwa, HI 96712") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.33310051533, + longitude = -157.922371535818, + ), + extra = + Bundle().apply { + putString("name", "Honolulu Airport (HNL) – Main") + putString("address", "300 Rogers Blvd, Honolulu, HI 96820") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.378981027427, + longitude = -157.930536387573, + ), + extra = + Bundle().apply { + putString("name", "Aiea Shopping Center") + putString("address", "99-115 Aiea Heights Drive #125, Aiea, HI 96701") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.38441101519, + longitude = -157.944839558127, + ), + extra = + Bundle().apply { + putString("name", "Pearlridge Center") + putString("address", "98-125 Kaonohi Street, Aiea, HI 96701") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.363785189939, + longitude = -157.928412704343, + ), + extra = + Bundle().apply { + putString("name", "Stadium Marketplace") + putString("address", "4561 Salt Lake Boulevard, Aiea, HI 96818") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.386340299119, + longitude = -157.941897795274, + ), + extra = + Bundle().apply { + putString("name", "Pearlridge Mall") + putString("address", "98-1005 Moanalua Road, Aiea, HI 96701") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 19.69971686484, + longitude = -155.067322812851, + ), + extra = + Bundle().apply { + putString("name", "Waiakea Center (Hilo)") + putString("address", "315-325 Makaala Street, Hilo, HI 96720") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 19.695097953188, + longitude = -155.06690203818, + ), + extra = + Bundle().apply { + putString("name", "Prince Kuhio Plaza (Hilo)") + putString("address", "111 East Puainako Street, Hilo, HI 96720") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 19.719877684807, + longitude = -155.082770375139, + ), + extra = + Bundle().apply { + putString("name", "Downtown Hilo (Kilauea Ave)") + putString("address", "438 Kilauea Ave, Hilo, HI 96720") + putBoolean("instore", true) + putBoolean("drive_through", true) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.33593, + longitude = -157.91581, + ), + extra = + Bundle().apply { + putString("name", "Airport Trade Center") + putString("address", "Airport Trade Center, 550 Paiea St, Honolulu, HI 96819") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.307358712377, + longitude = -157.865194116049, + ), + extra = + Bundle().apply { + putString("name", "Aloha Tower") + putString("address", "1 Aloha Tower Drive, Honolulu, HI 96813") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.30846253, + longitude = -157.8614898, + ), + extra = + Bundle().apply { + putString("name", "Bishop (Downtown)") + putString("address", "1000 Bishop Street #104, Honolulu, HI 96813") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.307604966533, + longitude = -157.860743724617, + ), + extra = + Bundle().apply { + putString("name", "Pickup – King & Alakea") + putString("address", "220 South King Street, Honolulu, HI 96813") + putBoolean("instore", false) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.285300825278, + longitude = -157.83841421971, + ), + extra = + Bundle().apply { + putString("name", "Discovery Bay Center") + putString("address", "1778 Ala Moana Boulevard, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.334058693598, + longitude = -158.023228524098, + ), + extra = + Bundle().apply { + putString("name", "Ewa Beach – Laulani Village") + putString("address", "91-1401 Fort Weaver Road, Ewa Beach, HI 96706") + putBoolean("instore", true) + putBoolean("drive_through", true) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.280578442859, + longitude = -157.828071689214, + ), + extra = + Bundle().apply { + putString("name", "DFS (Duty Free) Waikiki") + putString("address", "330 Royal Hawaiian Avenue, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.308557010703, + longitude = -157.862582769768, + ), + extra = + Bundle().apply { + putString("name", "Financial Plaza (Downtown)") + putString("address", "130 Merchant Street #111, Honolulu, HI 96813") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.282048, + longitude = -157.713041, + ), + extra = + Bundle().apply { + putString("name", "Hawaii Kai Town Center") + putString("address", "6700 Kalanianaole Highway, Honolulu, HI 96825") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.291792650634, + longitude = -157.849735879475, + ), + extra = + Bundle().apply { + putString("name", "Hokua (Ala Moana)") + putString("address", "1288 Ala Moana Blvd, Honolulu, HI 96814") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.335246981366, + longitude = -157.868748238078, + ), + extra = + Bundle().apply { + putString("name", "Kamehameha Shopping Center") + putString("address", "1620 North School Street, Honolulu, HI 96817") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.27852422, + longitude = -157.7875773, + ), + extra = + Bundle().apply { + putString("name", "Kahala Mall") + putString("address", "4211 Waialae Avenue, Honolulu, HI 96816") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.294505444307, + longitude = -157.841946089363, + ), + extra = + Bundle().apply { + putString("name", "Keeaumoku (WalMart)") + putString("address", "678 Keeaumoku Street #106, Honolulu, HI 96814") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.279056707748, + longitude = -157.813890137018, + ), + extra = + Bundle().apply { + putString("name", "Kapahulu Avenue") + putString("address", "625 Kapahulu Avenue, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.276148191143, + longitude = -157.704922547261, + ), + extra = + Bundle().apply { + putString("name", "Koko Marina Center") + putString("address", "7192 Kalanianaole Highway, Honolulu, HI 96825") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.30985278855, + longitude = -157.810260198584, + ), + extra = + Bundle().apply { + putString("name", "Manoa Valley") + putString("address", "2902 East Manoa Road, Honolulu, HI 96822") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.289750395336, + longitude = -157.843910788044, + ), + extra = + Bundle().apply { + putString("name", "Macy’s Ala Moana Center") + putString("address", "1450 Ala Moana Boulevard, Honolulu, HI 96814") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.341260775481, + longitude = -157.929507250967, + ), + extra = + Bundle().apply { + putString("name", "Moanalua Shopping Center") + putString("address", "930 Valkenburgh Street, Honolulu, HI 96818") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.278908517307, + longitude = -157.832413265507, + ), + extra = + Bundle().apply { + putString("name", "Outrigger Reef (Waikiki)") + putString("address", "2169 Kalia Road #102, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.279011840151, + longitude = -157.825557564916, + ), + extra = + Bundle().apply { + putString("name", "Ohana Waikiki West") + putString("address", "2330 Kuhio Avenue, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.30278387167, + longitude = -157.879450611886, + ), + extra = + Bundle().apply { + putString("name", "Sand Island") + putString("address", "120 Sand Island Access Road #4, Honolulu, HI 96819") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.289750395336, + longitude = -157.843910788044, + ), + extra = + Bundle().apply { + putString("name", "Sears Ala Moana Center") + putString("address", "1450 Ala Moana Blvd, Honolulu, HI 96814") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.2786604527, + longitude = -157.828371626919, + ), + extra = + Bundle().apply { + putString("name", "Waikiki Shopping Plaza") + putString("address", "2270 Kalakaua Avenue #1800, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.280533970958, + longitude = -157.82749796628, + ), + extra = + Bundle().apply { + putString("name", "Waikiki Trade Center (Reserve Bar)") + putString("address", "2255 Kuhio Avenue #S-1, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", true) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.293774306726, + longitude = -157.85297798269, + ), + extra = + Bundle().apply { + putString("name", "Ward Entertainment Center") + putString("address", "310 Kamakee Street #6, Honolulu, HI 96814") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.406095, + longitude = -157.800761, + ), + extra = + Bundle().apply { + putString("name", "Windward City Shopping Center") + putString("address", "45-480 Kaneohe Bay Drive, Kaneohe, HI 96744") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.279515520356, + longitude = -157.829265712704, + ), + extra = + Bundle().apply { + putString("name", "Waikiki Walk") + putString("address", "2222 Kalakaua Avenue, Honolulu, HI 96815") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.293340364607, + longitude = -157.85256477721, + ), + extra = + Bundle().apply { + putString("name", "Ward Gateway") + putString("address", "1142 Auahi Street, Honolulu, HI 96814") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.888659282451, + longitude = -156.477197459052, + ), + extra = + Bundle().apply { + putString("name", "Queen Kaahumanu Center") + putString("address", "275 West Kaahumanu Avenue #1200, Kahului, HI 96732") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.881960703032, + longitude = -156.45511618549, + ), + extra = + Bundle().apply { + putString("name", "Maui Marketplace") + putString("address", "270 Dairy Road, Kahului, HI 96732") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.393471214679, + longitude = -157.740438744365, + ), + extra = + Bundle().apply { + putString("name", "Kailua Village") + putString("address", "539 Kailua Road, Kailua, HI 96734") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 19.65018280057, + longitude = -155.987752998108, + ), + extra = + Bundle().apply { + putString("name", "Kona Coast Shopping Center") + putString("address", "74-5588 Palani Road, Kailua-Kona, HI 96740") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.020593379111, + longitude = -155.668585540658, + ), + extra = + Bundle().apply { + putString("name", "Parker Ranch Center") + putString("address", "67-1185 Mamalahoa Highway #D108, Kamuela, HI 96743") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.344027896932, + longitude = -158.11830127628, + ), + extra = + Bundle().apply { + putString("name", "Halekuai Center") + putString("address", "563 Farrington Highway #101, Kapolei, HI 96707") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.328579072139, + longitude = -158.086506230214, + ), + extra = + Bundle().apply { + putString("name", "Kapolei Parkway & Kamokila") + putString("address", "338 Kamokila Boulevard #108, Kapolei, HI 96797") + putBoolean("instore", true) + putBoolean("drive_through", true) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.734513943855, + longitude = -156.452970465534, + ), + extra = + Bundle().apply { + putString("name", "Kukui Mall") + putString("address", "1819 South Kihei Road, Kihei, HI 96738") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.750703062197, + longitude = -156.451408824978, + ), + extra = + Bundle().apply { + putString("name", "Piilani Village Shopping Center") + putString("address", "247 Piikea Avenue #106, Kihei, HI 96753") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 19.94001271876, + longitude = -155.856842731652, + ), + extra = + Bundle().apply { + putString("name", "Mauna Lani (Kohala Coast)") + putString("address", "68-1330 Mauna Lani Drive #H-101B, Kohala Coast, HI 96743") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.886244, + longitude = -156.684697, + ), + extra = + Bundle().apply { + putString("name", "Lahaina Cannery Mall") + putString("address", "1221 Honoapiilani Highway, Lahaina, HI 96761") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.877708110758, + longitude = -156.679031878844, + ), + extra = + Bundle().apply { + putString("name", "Lahaina (Front Street)") + putString("address", "845 Wainee Street, Lahaina, HI 96761") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.969553724378, + longitude = -159.388283368972, + ), + extra = + Bundle().apply { + putString("name", "Kukui Grove Center") + putString("address", "3-2600 Kaumualii Highway #A8, Lihue, HI 96766") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.889156401906, + longitude = -156.449318101378, + ), + extra = + Bundle().apply { + putString("name", "Kahului Airport (OGG)") + putString("address", "1 Keolani Place, Kahului, HI 96732") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.458431746129, + longitude = -158.015862355331, + ), + extra = + Bundle().apply { + putString("name", "Mililani Shopping Center") + putString("address", "95-221 Kipapa Drive, Mililani, HI 96789") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.453888574294, + longitude = -158.007690940987, + ), + extra = + Bundle().apply { + putString("name", "Mililani Town Center") + putString("address", "95-1249 Meheula Parkway, Mililani, HI 96789") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 20.838230357792, + longitude = -156.342698446307, + ), + extra = + Bundle().apply { + putString("name", "Pukalani Foodland Center") + putString("address", "55 Pukalani Street, Pukalani, HI 96768") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.378675, + longitude = -157.728499, + ), + extra = + Bundle().apply { + putString("name", "Enchanted Lake Center (Kailua)") + putString("address", "1020 Keolu Drive, Kailua, HI 96734") + putBoolean("instore", true) + putBoolean("drive_through", true) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.389003512015, + longitude = -158.033431400538, + ), + extra = + Bundle().apply { + putString("name", "Kunia Shopping Center") + putString("address", "94-673 Kupuohi Street, Waipahu, HI 96797") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.401138284746, + longitude = -158.010288643364, + ), + extra = + Bundle().apply { + putString("name", "Waikele Center") + putString("address", "94-799 Lumiaina Street, Waipahu, HI 96797") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.881002766882, + longitude = -159.457726341723, + ), + extra = + Bundle().apply { + putString("name", "Poipu Shopping Village") + putString("address", "2360 Kiahuna Plantation Drive #E70, Koloa, HI 96756") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.966834230955, + longitude = -159.381526209527, + ), + extra = + Bundle().apply { + putString("name", "Safeway Lihue") + putString("address", "4454 Nuhou Street, Lihue, HI 96766") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.970935289068, + longitude = -159.375643172372, + ), + extra = + Bundle().apply { + putString("name", "Target Lihue (Kauai)") + putString("address", "4303 Nawiliwili Road, Lihue, HI 96766") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 22.061786387888, + longitude = -159.320539848567, + ), + extra = + Bundle().apply { + putString("name", "Kauai Village SC (Kapaa)") + putString("address", "4-831 Kuhio Highway #208, Kapaa, HI 96746") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.32874860701, + longitude = -158.091318912219, + ), + extra = + Bundle().apply { + putString("name", "Target Kapolei") + putString("address", "4450 Kapolei Parkway, Kapolei, HI 96707") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.340541119127, + longitude = -158.124703887408, + ), + extra = + Bundle().apply { + putString("name", "Ko Olina Station") + putString("address", "92-1047 Olani Street, Kapolei, HI 96707") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.328151560875, + longitude = -158.021804173199, + ), + extra = + Bundle().apply { + putString("name", "Laulani Village (Ewa Beach)") + putString("address", "91-1105 Keaunui Drive #500, Ewa Beach, HI 96706") + putBoolean("instore", true) + putBoolean("drive_through", true) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.34213, + longitude = -157.95157, + ), + extra = + Bundle().apply { + putString("name", "Hickam AFB (Base Access)") + putString("address", "Bldg B-1250, Hickam AFB, Honolulu, HI 96853") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.349070820935, + longitude = -157.932730132699, + ), + extra = + Bundle().apply { + putString("name", "Pearl Harbor NEX") + putString("address", "4725 Bougainville Drive, Honolulu, HI 96818") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + MarkerState( + position = + GeoPoint( + latitude = 21.356011085979, + longitude = -157.893896231076, + ), + extra = + Bundle().apply { + putString("name", "Tripler Army Medical Center") + putString("address", "1 Jarrett White Road, Honolulu, HI 96859") + putBoolean("instore", true) + putBoolean("drive_through", false) + putBoolean("only_reserved", false) + }, + ), + ) diff --git a/example-app/src/main/java/com/mapconductor/example/AppViewModel.kt b/example-app/src/main/java/com/mapconductor/example/AppViewModel.kt index b3275b45..77f887df 100644 --- a/example-app/src/main/java/com/mapconductor/example/AppViewModel.kt +++ b/example-app/src/main/java/com/mapconductor/example/AppViewModel.kt @@ -1,8 +1,8 @@ package com.mapconductor.example import androidx.lifecycle.ViewModel -import com.mapconductor.core.MapCameraPositionBase import com.mapconductor.core.features.GeoPoint +import com.mapconductor.core.map.MapCameraPositionBase import com.mapconductor.core.map.MapViewState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -11,23 +11,30 @@ import kotlinx.coroutines.flow.asStateFlow interface AppViewModel { val initCameraPosition: MapCameraPositionBase val state: StateFlow?> + fun changeState(state: MapViewState<*>) + fun flyTo(listener: MapViewState.MoveCameraCallback? = null) + fun onCallButtonClick() } -class AppViewModelImpl: ViewModel(), AppViewModel { +class AppViewModelImpl : + ViewModel(), + AppViewModel { // カメラの初期位置 - override val initCameraPosition = MapCameraPositionBase( - position = GeoPoint.fromLatLong( - latitude = 21.382314, - longitude = -157.933097, - ), - zoom = 10.0, - bearing = 0.0, - tilt = 0.0, - paddings = null, - ) + override val initCameraPosition = + MapCameraPositionBase( + position = + GeoPoint.fromLatLong( + latitude = 21.382314, + longitude = -157.933097, + ), + zoom = 10.0, + bearing = 0.0, + tilt = 0.0, + paddings = null, + ) private val _state = MutableStateFlow?>(null) override val state: StateFlow?> = _state.asStateFlow() @@ -38,21 +45,22 @@ class AppViewModelImpl: ViewModel(), AppViewModel { override fun flyTo(listener: MapViewState.MoveCameraCallback?) { this@AppViewModelImpl.state.value?.moveCameraTo( - position = MapCameraPositionBase( - position = GeoPoint( - latitude = 40.689184289566214, - longitude = -74.04454331830473, + position = + MapCameraPositionBase( + position = + GeoPoint( + latitude = 40.689184289566214, + longitude = -74.04454331830473, + ), + tilt = 70.0, + zoom = 18.0, ), - tilt = 70.0, - zoom = 18.0, - ), durationMs = 3000, listener = listener, ) } override fun onCallButtonClick() { - } override fun onCleared() { diff --git a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt index b28e4a3c..95c8d876 100644 --- a/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt +++ b/example-app/src/main/java/com/mapconductor/example/DemoAppScreen.kt @@ -1,6 +1,5 @@ package com.mapconductor.example -import android.os.Bundle import androidx.appcompat.content.res.AppCompatResources import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -41,69 +40,71 @@ import com.mapconductor.here.HereMapDesign import com.mapconductor.here.rememberHereMapViewState import com.mapconductor.mapbox.MapboxMapDesign import com.mapconductor.mapbox.rememberMapboxMapViewState +import android.os.Bundle @OptIn(ExperimentalMaterial3Api::class) @Composable -fun DemoAppScreen( - appViewModel: AppViewModel, -) { - +fun DemoAppScreen(appViewModel: AppViewModel) { // ---------- GoogleMaps --------------- - val googleMapState = rememberGoogleMapViewState( - mapDesign = GoogleMapDesign.Normal, - cameraPosition = appViewModel.initCameraPosition, - ) + val googleMapState = + rememberGoogleMapViewState( + mapDesign = GoogleMapDesign.Normal, + cameraPosition = appViewModel.initCameraPosition, + ) // ---------- Mapbox --------------- - val mapboxMapState = rememberMapboxMapViewState( - mapDesign = MapboxMapDesign.Standard, - cameraPosition = appViewModel.initCameraPosition, - ) + val mapboxMapState = + rememberMapboxMapViewState( + mapDesign = MapboxMapDesign.Standard, + cameraPosition = appViewModel.initCameraPosition, + ) // ---------- Here --------------- - val hereMapState = rememberHereMapViewState( - mapDesign = HereMapDesign.NormalDay, - cameraPosition = appViewModel.initCameraPosition, - ) + val hereMapState = + rememberHereMapViewState( + mapDesign = HereMapDesign.NormalDay, + cameraPosition = appViewModel.initCameraPosition, + ) // ---------- ArcGIS --------------- - val elevationSources = listOf( - "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer", - ) - val arcGISMapState = rememberArcGISMapViewState( - mapDesign = ArcGISDesign.Streets.withElevationSources(elevationSources), - cameraPosition = appViewModel.initCameraPosition, - ) - val menuItems = listOf( - IconItem( - key = "googlemap", - label = "Google Map", - lightIconResId = R.drawable.google_maps_logo, - darkIconResId = R.drawable.google_maps_logo, - value = googleMapState, - ), - - IconItem( - key = "mapbox", - label = "Mapbox", - lightIconResId = R.drawable.mapbox_logo_black, - darkIconResId = R.drawable.mapbox_logo_white, - value = mapboxMapState, - ), - - IconItem( - key = "heremap", - label = "Here", - lightIconResId = R.drawable.here_logo_black, - darkIconResId = R.drawable.here_logo_white, - value = hereMapState, - ), - IconItem( - key = "arcgis", - label = "ArcGIS", - lightIconResId = R.drawable.arcgis_logo_black, - darkIconResId = R.drawable.arcgis_logo_white, - value = arcGISMapState, - ), - ) + val elevationSources = + listOf( + "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer", + ) + val arcGISMapState = + rememberArcGISMapViewState( + mapDesign = ArcGISDesign.Streets.withElevationSources(elevationSources), + cameraPosition = appViewModel.initCameraPosition, + ) + val menuItems = + listOf( + IconItem( + key = "googlemap", + label = "Google Map", + lightIconResId = R.drawable.google_maps_logo, + darkIconResId = R.drawable.google_maps_logo, + value = googleMapState, + ), + IconItem( + key = "mapbox", + label = "Mapbox", + lightIconResId = R.drawable.mapbox_logo_black, + darkIconResId = R.drawable.mapbox_logo_white, + value = mapboxMapState, + ), + IconItem( + key = "heremap", + label = "Here", + lightIconResId = R.drawable.here_logo_black, + darkIconResId = R.drawable.here_logo_white, + value = hereMapState, + ), + IconItem( + key = "arcgis", + label = "ArcGIS", + lightIconResId = R.drawable.arcgis_logo_black, + darkIconResId = R.drawable.arcgis_logo_white, + value = arcGISMapState, + ), + ) val context = LocalContext.current var selectedIndex by rememberSaveable { mutableIntStateOf(2) } @@ -112,19 +113,21 @@ fun DemoAppScreen( } val drawable = AppCompatResources.getDrawable(context, R.drawable.coffee_svg) - val icon = MarkerIconProp( - iconDrawable = drawable, - fillColor = Color(0x6f, 0x4e, 0x37).toArgb(), - strokeColor = Color.LightGray.toArgb() - ) + val icon = + MarkerIconProp( + iconDrawable = drawable, + fillColor = Color(0x6f, 0x4e, 0x37).toArgb(), + strokeColor = Color.LightGray.toArgb(), + ) val messages = remember { mutableStateListOf() } fun showToast(text: String) { - messages += ToastMessage( - text = text, - onDismiss = { messages.removeIf { it.text == text } } - ) + messages += + ToastMessage( + text = text, + onDismiss = { messages.removeIf { it.text == text } }, + ) } AppTheme { @@ -139,7 +142,7 @@ fun DemoAppScreen( modifier = Modifier.weight(0.2f), itemList = menuItems, selectedIndex = selectedIndex, - onSelect = { index, _ -> selectedIndex = index } + onSelect = { index, _ -> selectedIndex = index }, ) Button( modifier = Modifier.weight(0.1f), @@ -148,7 +151,7 @@ fun DemoAppScreen( Text("Fly to!") } } - } + }, ) }, modifier = Modifier.fillMaxSize(), @@ -156,14 +159,14 @@ fun DemoAppScreen( Box { MapArea( state = appViewModel.state.collectAsStateWithLifecycle().value, - markers = StarbucksHI_list.slice(IntRange(0, 10)).map { - MarkerState( - position = it.position, - extra = it.extra, - icon = icon, - + markers = + StarbucksHI_list.slice(IntRange(0, 10)).map { + MarkerState( + position = it.position, + extra = it.extra, + icon = icon, ) - }, + }, onCallButtonClick = { showToast("clicked") }, @@ -174,7 +177,7 @@ fun DemoAppScreen( ) ToastHost( messages = messages, - onDismiss = { messages.remove(it) } + onDismiss = { messages.remove(it) }, ) } } diff --git a/example-app/src/main/java/com/mapconductor/example/MainActivity.kt b/example-app/src/main/java/com/mapconductor/example/MainActivity.kt index 3abba7e4..b1165a61 100644 --- a/example-app/src/main/java/com/mapconductor/example/MainActivity.kt +++ b/example-app/src/main/java/com/mapconductor/example/MainActivity.kt @@ -1,6 +1,5 @@ package com.mapconductor.example -import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge @@ -12,9 +11,10 @@ import com.mapconductor.StarbucksHI_list import com.mapconductor.arcgis.ArcGISMapView import com.mapconductor.arcgis.MapCameraPosition import com.mapconductor.arcgis.rememberArcGISMapViewState +import android.os.Bundle class MainActivity : ComponentActivity() { - private val appViewModel : AppViewModelImpl by viewModels() + private val appViewModel: AppViewModelImpl by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -29,18 +29,18 @@ class MainActivity : ComponentActivity() { @Composable fun ArcGISScreen() { - val mapState = rememberArcGISMapViewState( - cameraPosition = MapCameraPosition( - position = StarbucksHI_list[0].position, - zoom = 13.0, + val mapState = + rememberArcGISMapViewState( + cameraPosition = + MapCameraPosition( + position = StarbucksHI_list[0].position, + zoom = 13.0, + ), ) - ) ArcGISMapView( state = mapState, - modifier = Modifier.fillMaxSize() + modifier = Modifier.fillMaxSize(), ) { } - } - diff --git a/example-app/src/main/java/com/mapconductor/example/MapArea.kt b/example-app/src/main/java/com/mapconductor/example/MapArea.kt index 73e4f346..a994396d 100644 --- a/example-app/src/main/java/com/mapconductor/example/MapArea.kt +++ b/example-app/src/main/java/com/mapconductor/example/MapArea.kt @@ -37,7 +37,6 @@ fun MapArea( MapViewContainer( state = state, ) { - markers.forEach { state -> Marker( state = state, @@ -58,22 +57,23 @@ fun MapArea( } } Column( - modifier = Modifier - .align(Alignment.TopEnd) - .background(Color( - red = 0.9f, - green = 0.9f, - blue = 0.9f, - alpha = 0.75f, - )) - .wrapContentHeight() + modifier = + Modifier + .align(Alignment.TopEnd) + .background( + Color( + red = 0.9f, + green = 0.9f, + blue = 0.9f, + alpha = 0.75f, + ), + ).wrapContentHeight(), ) { Text("LatLng: (${camera?.position?.latitude}, ${camera?.position?.longitude})", color = Color.Black) Text("Zoom: ${camera?.zoom}", color = Color.Black) Text("bearing: ${camera?.bearing}", color = Color.Black) Text("tilt: ${camera?.tilt}", color = Color.Black) } - } else { Text( text = "Loading...", diff --git a/example-app/src/main/java/com/mapconductor/example/MapView.kt b/example-app/src/main/java/com/mapconductor/example/MapView.kt index ab09c6f6..d1094304 100644 --- a/example-app/src/main/java/com/mapconductor/example/MapView.kt +++ b/example-app/src/main/java/com/mapconductor/example/MapView.kt @@ -27,21 +27,24 @@ fun MapViewContainer( content = content, ) } - is HereMapViewState -> HereMapView( - modifier = modifier, - state = state, - content = content, - ) - is MapboxMapViewState -> MapboxMapView( - modifier = modifier, - state = state, - content = content, - ) - is ArcGISMapViewState -> ArcGISMapView( - modifier = modifier, - state = state, - content = content, - ) + is HereMapViewState -> + HereMapView( + modifier = modifier, + state = state, + content = content, + ) + is MapboxMapViewState -> + MapboxMapView( + modifier = modifier, + state = state, + content = content, + ) + is ArcGISMapViewState -> + ArcGISMapView( + modifier = modifier, + state = state, + content = content, + ) else -> throw IllegalStateException("unknown state") } -} \ No newline at end of file +} diff --git a/example-app/src/main/java/com/mapconductor/example/demo/StoreCard.kt b/example-app/src/main/java/com/mapconductor/example/demo/StoreCard.kt index d4d7a80d..fd0b223a 100644 --- a/example-app/src/main/java/com/mapconductor/example/demo/StoreCard.kt +++ b/example-app/src/main/java/com/mapconductor/example/demo/StoreCard.kt @@ -1,6 +1,5 @@ package com.mapconductor.example.demo -import android.content.res.Configuration import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -31,23 +30,22 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.mapconductor.example.R +import android.content.res.Configuration @Preview(widthDp = 600, heightDp = 300, uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(widthDp = 400, heightDp = 600) @Composable -fun StoreCard( - onClick: () -> Unit = {}, -) { - +fun StoreCard(onClick: () -> Unit = {}) { @Composable fun CallButtonPortrait(modifier: Modifier = Modifier) { Button( onClick = onClick, modifier = modifier, shape = CircleShape, - colors = ButtonDefaults.buttonColors( - containerColor = Color(0xFF00704A), - ) + colors = + ButtonDefaults.buttonColors( + containerColor = Color(0xFF00704A), + ), ) { Row { Icon( @@ -67,10 +65,11 @@ fun StoreCard( fun CallButtonLandScape(modifier: Modifier = Modifier) { Button( onClick = onClick, - modifier = modifier - .heightIn(max = 100.dp) - .fillMaxHeight(), - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF00704A)) + modifier = + modifier + .heightIn(max = 100.dp) + .fillMaxHeight(), + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF00704A)), ) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Icon( @@ -103,7 +102,7 @@ fun StoreCard( contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize(), - alpha = 0.1f + alpha = 0.1f, ) } @@ -112,15 +111,16 @@ fun StoreCard( @Composable fun PortraitLayout(modifier: Modifier = Modifier) { Box( - modifier = modifier + modifier = modifier, ) { BackgroundImage() Column(modifier = Modifier.padding(10.dp)) { StoreInfo(modifier = Modifier.padding(10.dp)) CallButtonPortrait( - modifier = Modifier - .fillMaxWidth() - .padding(10.dp) + modifier = + Modifier + .fillMaxWidth() + .padding(10.dp), ) } } @@ -132,18 +132,20 @@ fun StoreCard( BackgroundImage() StoreInfo(modifier = Modifier.padding(10.dp)) CallButtonLandScape( - modifier = Modifier - .align(alignment = Alignment.CenterEnd) - .padding(end = 10.dp) + modifier = + Modifier + .align(alignment = Alignment.CenterEnd) + .padding(end = 10.dp), ) } } - val modifier = Modifier - .widthIn(max = 350.dp) - .wrapContentSize() + val modifier = + Modifier + .widthIn(max = 350.dp) + .wrapContentSize() if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { LandscapeLayout(modifier = modifier) } else { PortraitLayout(modifier = modifier) } -} \ No newline at end of file +} diff --git a/example-app/src/main/java/com/mapconductor/example/demo/TableRow.kt b/example-app/src/main/java/com/mapconductor/example/demo/TableRow.kt index f8ee33d0..1d4355c2 100644 --- a/example-app/src/main/java/com/mapconductor/example/demo/TableRow.kt +++ b/example-app/src/main/java/com/mapconductor/example/demo/TableRow.kt @@ -11,10 +11,15 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @Composable -fun TableRow(label: String, value: String) { - Row(modifier = Modifier - .fillMaxWidth() - .padding(vertical = 4.dp) +fun TableRow( + label: String, + value: String, +) { + Row( + modifier = + Modifier + .fillMaxWidth() + .padding(vertical = 4.dp), ) { Text( text = label, @@ -25,7 +30,7 @@ fun TableRow(label: String, value: String) { Text( text = value, modifier = Modifier.weight(1f), - fontSize = 16.sp + fontSize = 16.sp, ) } } diff --git a/example-app/src/main/java/com/mapconductor/example/toast/Toast.kt b/example-app/src/main/java/com/mapconductor/example/toast/Toast.kt index 41fa9834..658e0c8b 100644 --- a/example-app/src/main/java/com/mapconductor/example/toast/Toast.kt +++ b/example-app/src/main/java/com/mapconductor/example/toast/Toast.kt @@ -1,22 +1,16 @@ package com.mapconductor.example.toast import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -37,23 +31,24 @@ import kotlinx.coroutines.delay fun ToastHost( messages: List, onDismiss: (ToastMessage) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Box( modifier = modifier.fillMaxSize(), - contentAlignment = Alignment.BottomCenter + contentAlignment = Alignment.BottomCenter, ) { Column( verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .padding(bottom = 48.dp) + modifier = + Modifier + .padding(bottom = 48.dp), ) { // 下から順に表示(最新は一番上) messages.reversed().forEach { message -> ToastItem( message = message, - onDismiss = { onDismiss(message) } + onDismiss = { onDismiss(message) }, ) } } @@ -63,7 +58,7 @@ fun ToastHost( @Composable fun ToastItem( message: ToastMessage, - onDismiss: () -> Unit + onDismiss: () -> Unit, ) { var visible by remember { mutableStateOf(true) } @@ -77,22 +72,22 @@ fun ToastItem( AnimatedVisibility( visible = visible, enter = slideInVertically(initialOffsetY = { it }) + fadeIn(), - exit = slideOutVertically(targetOffsetY = { it }) + fadeOut() + exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(), ) { Card( shape = RoundedCornerShape(12.dp), elevation = CardDefaults.cardElevation(8.dp), colors = CardDefaults.cardColors(containerColor = Color(0xFF323232)), - modifier = Modifier - .padding(4.dp) - .fillMaxWidth(0.85f) + modifier = + Modifier + .padding(4.dp) + .fillMaxWidth(0.85f), ) { Text( text = message.text, color = Color.White, - modifier = Modifier.padding(16.dp) + modifier = Modifier.padding(16.dp), ) } } } - diff --git a/example-app/src/main/java/com/mapconductor/example/toast/ToastMessage.kt b/example-app/src/main/java/com/mapconductor/example/toast/ToastMessage.kt index a277f901..47b5aa18 100644 --- a/example-app/src/main/java/com/mapconductor/example/toast/ToastMessage.kt +++ b/example-app/src/main/java/com/mapconductor/example/toast/ToastMessage.kt @@ -6,5 +6,5 @@ data class ToastMessage( val id: UUID = UUID.randomUUID(), val text: String, val durationMillis: Long = 3000L, - val onDismiss: () -> Unit + val onDismiss: () -> Unit, ) diff --git a/example-app/src/main/java/com/mapconductor/example/ui/IconSelectMenu.kt b/example-app/src/main/java/com/mapconductor/example/ui/IconSelectMenu.kt index d6d56b44..abd9ade8 100644 --- a/example-app/src/main/java/com/mapconductor/example/ui/IconSelectMenu.kt +++ b/example-app/src/main/java/com/mapconductor/example/ui/IconSelectMenu.kt @@ -46,14 +46,13 @@ fun IconSelectMenu( println("Selected index: $index, item: $item") }, ) { - var expanded by remember { mutableStateOf(false) } val selected = itemList.get(selectedIndex) ExposedDropdownMenuBox( expanded = expanded, modifier = modifier, - onExpandedChange = { expanded = !expanded } + onExpandedChange = { expanded = !expanded }, ) { TextField( readOnly = true, @@ -66,35 +65,44 @@ fun IconSelectMenu( leadingIcon = { with(selected) { Icon( - painter = painterResource(id = when(isDark) { - true -> darkIconResId - false -> lightIconResId - }), + painter = + painterResource( + id = + when (isDark) { + true -> darkIconResId + false -> lightIconResId + }, + ), contentDescription = label, modifier = Modifier.size(30.dp), tint = Color.Unspecified, ) } }, - modifier = Modifier - .menuAnchor(MenuAnchorType.PrimaryEditable) - .fillMaxWidth() + modifier = + Modifier + .menuAnchor(MenuAnchorType.PrimaryEditable) + .fillMaxWidth(), ) ExposedDropdownMenu( expanded = expanded, modifier = modifier, - onDismissRequest = { expanded = false } + onDismissRequest = { expanded = false }, ) { itemList.forEachIndexed { index, item -> DropdownMenuItem( text = { Row(verticalAlignment = Alignment.CenterVertically) { Icon( - painter = painterResource(id = when(isDark) { - true -> item.darkIconResId - false -> item.lightIconResId - }), + painter = + painterResource( + id = + when (isDark) { + true -> item.darkIconResId + false -> item.lightIconResId + }, + ), contentDescription = item.label, modifier = Modifier.size(30.dp), tint = Color.Unspecified, @@ -106,9 +114,9 @@ fun IconSelectMenu( onClick = { expanded = false onSelect(index, item) - } + }, ) } } } -} \ No newline at end of file +} diff --git a/example-app/src/main/java/com/mapconductor/example/ui/theme/Color.kt b/example-app/src/main/java/com/mapconductor/example/ui/theme/Color.kt index 7b0be8c0..7a871766 100644 --- a/example-app/src/main/java/com/mapconductor/example/ui/theme/Color.kt +++ b/example-app/src/main/java/com/mapconductor/example/ui/theme/Color.kt @@ -8,4 +8,4 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) diff --git a/example-app/src/main/java/com/mapconductor/example/ui/theme/Theme.kt b/example-app/src/main/java/com/mapconductor/example/ui/theme/Theme.kt index 24620a27..6fe9db75 100644 --- a/example-app/src/main/java/com/mapconductor/example/ui/theme/Theme.kt +++ b/example-app/src/main/java/com/mapconductor/example/ui/theme/Theme.kt @@ -7,18 +7,19 @@ import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -private val DarkColorScheme = darkColorScheme( - primary = Purple80, - secondary = PurpleGrey80, - tertiary = Pink80 -) - -private val LightColorScheme = lightColorScheme( - primary = Purple40, - secondary = PurpleGrey40, - tertiary = Pink40, - background = Color.White, +private val DarkColorScheme = + darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80, + ) +private val LightColorScheme = + lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40, + background = Color.White, /* Other default colors to override surface = Color(0xFFFFFBFE), onPrimary = Color.White, @@ -26,21 +27,21 @@ private val LightColorScheme = lightColorScheme( onTertiary = Color.White, onBackground = Color(0xFF1C1B1F), onSurface = Color(0xFF1C1B1F), - */ -) + */ + ) @Composable fun AppTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme MaterialTheme( colorScheme = colorScheme, typography = Typography, - content = content + content = content, ) -} \ No newline at end of file +} diff --git a/example-app/src/main/java/com/mapconductor/example/ui/theme/Type.kt b/example-app/src/main/java/com/mapconductor/example/ui/theme/Type.kt index 2976083c..99b89c4a 100644 --- a/example-app/src/main/java/com/mapconductor/example/ui/theme/Type.kt +++ b/example-app/src/main/java/com/mapconductor/example/ui/theme/Type.kt @@ -7,14 +7,16 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp // Set of Material typography styles to start with -val Typography = Typography( - bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) +val Typography = + Typography( + bodyLarge = + TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp, + ), /* Other default text styles to override titleLarge = TextStyle( fontFamily = FontFamily.Default, @@ -30,5 +32,5 @@ val Typography = Typography( lineHeight = 16.sp, letterSpacing = 0.5.sp ) - */ -) \ No newline at end of file + */ + ) diff --git a/example-app/src/test/java/com/mapconductor/example/ExampleUnitTest.kt b/example-app/src/test/java/com/mapconductor/example/ExampleUnitTest.kt index 34a142d5..01a5ce6a 100644 --- a/example-app/src/test/java/com/mapconductor/example/ExampleUnitTest.kt +++ b/example-app/src/test/java/com/mapconductor/example/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.mapconductor.example +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/mapconductor-core/build.gradle.kts b/mapconductor-core/build.gradle.kts index 4e2c8ab2..18fb0dab 100644 --- a/mapconductor-core/build.gradle.kts +++ b/mapconductor-core/build.gradle.kts @@ -2,9 +2,17 @@ plugins { id("com.android.library") id("org.jetbrains.kotlin.android") id("maven-publish") - alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.compose) + id("org.jlleitschuh.gradle.ktlint") } +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } +} android { namespace = "com.mapconductor.core" @@ -28,7 +36,7 @@ android { isMinifyEnabled = project.property("isMinifyEnabled").toString().toBoolean() proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } diff --git a/mapconductor-core/src/androidTest/java/com/mapconductor/core/ExampleInstrumentedTest.kt b/mapconductor-core/src/androidTest/java/com/mapconductor/core/ExampleInstrumentedTest.kt index a5302162..2f0554e4 100644 --- a/mapconductor-core/src/androidTest/java/com/mapconductor/core/ExampleInstrumentedTest.kt +++ b/mapconductor-core/src/androidTest/java/com/mapconductor/core/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.mapconductor.core -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.mapconductor.core", appContext.packageName) } -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/InitProvider.kt b/mapconductor-core/src/main/java/com/mapconductor/core/InitProvider.kt index 24a0c300..00cae611 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/InitProvider.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/InitProvider.kt @@ -19,27 +19,26 @@ class InitProvider : ContentProvider() { projection: Array?, selection: String?, selectionArgs: Array?, - sortOrder: String? + sortOrder: String?, ): Cursor? = null override fun getType(uri: Uri): String? = null override fun insert( uri: Uri, - values: ContentValues? + values: ContentValues?, ): Uri? = null override fun delete( uri: Uri, selection: String?, - selectionArgs: Array? + selectionArgs: Array?, ): Int = 0 override fun update( uri: Uri, values: ContentValues?, selection: String?, - selectionArgs: Array? + selectionArgs: Array?, ): Int = 0 - -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MapCameraPositionBase.kt b/mapconductor-core/src/main/java/com/mapconductor/core/MapCameraPositionBase.kt deleted file mode 100644 index bec8d8a2..00000000 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MapCameraPositionBase.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.mapconductor.core - -import com.mapconductor.core.features.IGeoPoint - -interface IMapCameraPosition { - val position: IGeoPoint - val zoom: Double - val bearing: Double - val tilt: Double - val paddings: MapPaddings? -} - -open class MapCameraPositionBase( - override val position: IGeoPoint, - override val zoom: Double = 0.0, - override val bearing: Double = 0.0, - override val tilt: Double = 0.0, - override val paddings: MapPaddings? = MapPaddingsImpl.Zeros, -): IMapCameraPosition { - companion object { - val Default = MapCameraPositionBase( - position = object : IGeoPoint { - override val latitude: Double = 0.0 - override val longitude: Double = 0.0 - override val altitude: Double? = null - - fun toUrlValue(precision: Int): String { - return "0.0,0.0" - } - }, - zoom = 0.0, - bearing = 0.0, - tilt = 0.0, - ) - } -} \ No newline at end of file diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MapOverlayRegistry.kt b/mapconductor-core/src/main/java/com/mapconductor/core/MapOverlayRegistry.kt deleted file mode 100644 index eedb5599..00000000 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MapOverlayRegistry.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.mapconductor.core - -import com.mapconductor.core.controller.MapViewController -import kotlinx.coroutines.flow.StateFlow - -interface MapOverlay { - val flow: StateFlow> - suspend fun render(data: List, controller: MapViewController) -} - -class MapOverlayRegistry { - private val overlays = mutableListOf>() - - fun register(overlay: MapOverlay<*>) { - if (overlays.toSet().contains(overlay)) return - overlays.add(overlay) - } - - fun getAll(): List> = overlays -} \ No newline at end of file diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MapPaddingsImpl.kt b/mapconductor-core/src/main/java/com/mapconductor/core/MapPaddingsImpl.kt deleted file mode 100644 index b7eb3191..00000000 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MapPaddingsImpl.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.mapconductor.core - -interface MapPaddings { - val top: Double - val left: Double - val bottom: Double - val right: Double - -} -open class MapPaddingsImpl @JvmOverloads constructor( - override val top: Double = 0.0, - override val left: Double = 0.0, - override val bottom: Double = 0.0, - override val right: Double = 0.0, -): MapPaddings { - companion object { - val Zeros: MapPaddingsImpl = MapPaddingsImpl(0.0, 0.0, 0.0, 0.0) - - fun from(paddingsImpl: MapPaddings) = when(paddingsImpl) { - is MapPaddingsImpl -> paddingsImpl - else -> MapPaddingsImpl( - top = paddingsImpl.top, - left = paddingsImpl.left, - bottom = paddingsImpl.bottom, - right = paddingsImpl.right, - ) - } - } -} \ No newline at end of file diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MarkerManager.kt b/mapconductor-core/src/main/java/com/mapconductor/core/MarkerManager.kt index 6fae5c54..73312a50 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MarkerManager.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/MarkerManager.kt @@ -1,5 +1,19 @@ package com.mapconductor.core +import androidx.compose.ui.geometry.Size +import androidx.core.graphics.createBitmap +import androidx.core.graphics.scale +import androidx.core.graphics.withScale +import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.geocell.HexCell +import com.mapconductor.core.geocell.HexCellRegistry +import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.marker.BitmapIcon +import com.mapconductor.core.marker.MarkerEntry +import com.mapconductor.core.marker.MarkerIconProp +import com.mapconductor.core.spherical.haversineDistance +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentMap import android.graphics.Bitmap import android.graphics.BitmapShader import android.graphics.BlurMaskFilter @@ -15,23 +29,10 @@ import android.graphics.Typeface import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.util.LruCache -import androidx.compose.ui.geometry.Size -import androidx.core.graphics.createBitmap -import androidx.core.graphics.scale -import androidx.core.graphics.withScale -import com.mapconductor.core.features.IGeoPoint -import com.mapconductor.core.geocell.HexCell -import com.mapconductor.core.geocell.HexCellRegistry -import com.mapconductor.core.geocell.HexGeocell -import com.mapconductor.core.marker.BitmapIcon -import com.mapconductor.core.marker.MarkerEntry -import com.mapconductor.core.marker.MarkerIconProp -import com.mapconductor.core.spherical.haversineDistance -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap - -class MarkerManager(geocell: HexGeocell) { +class MarkerManager( + geocell: HexGeocell, +) { val bitmapCache: LruCache by lazy { // Get max memory size by bytes val maxMemory = Runtime.getRuntime().maxMemory() @@ -39,35 +40,36 @@ class MarkerManager(geocell: HexGeocell) { // Cache bytes object : LruCache(cacheSize.toInt()) { - override fun sizeOf(key: Int, iconRes: BitmapIcon): Int { - return iconRes.bitmap.byteCount / 1024 - } + override fun sizeOf( + key: Int, + iconRes: BitmapIcon, + ): Int = iconRes.bitmap.byteCount / 1024 } } - private val _markers: ConcurrentHashMap = ConcurrentHashMap() - private val _entries: ConcurrentMap = ConcurrentHashMap() - private val _cellRegistry = HexCellRegistry( - geocell = geocell, - - // Maximum zoom level - zoom = 20.0, - ) + private val markers: ConcurrentHashMap = ConcurrentHashMap() + private val entries: ConcurrentMap = ConcurrentHashMap() + private val cellRegistry = + HexCellRegistry( + geocell = geocell, + // Maximum zoom level + zoom = 20.0, + ) - fun containsKey(id: String): Boolean = _markers.containsKey(id) + fun containsKey(id: String): Boolean = markers.containsKey(id) - fun equalsValue(entry: MarkerEntry): Boolean = _entries.get(entry.id)?.equals(entry) ?: false + fun equalsValue(entry: MarkerEntry): Boolean = entries.get(entry.id)?.equals(entry) ?: false - fun getValueSet(): Set = _entries.values.toSet() + fun getValueSet(): Set = entries.values.toSet() - fun getMarker(id: String): ActualMarker? = _markers.get(id) + fun getMarker(id: String): ActualMarker? = markers.get(id) - fun getEntry(id: String): MarkerEntry? = _entries.get(id) + fun getEntry(id: String): MarkerEntry? = entries.get(id) fun removeEntry(id: String) { - _markers.remove(id) - _entries.remove(id)?.let { - _cellRegistry.removePoint(it) + markers.remove(id) + entries.remove(id)?.let { + cellRegistry.removePoint(it) } } @@ -75,64 +77,69 @@ class MarkerManager(geocell: HexGeocell) { position: IGeoPoint, zoom: Double, pixels: Double, - tileSize: Int = 256 - ): Double = _cellRegistry.metersPerPixel(position, zoom, pixels, tileSize) + tileSize: Int = 256, + ): Double = cellRegistry.metersPerPixel(position, zoom, pixels, tileSize) fun findNearest(position: IGeoPoint): MarkerEntry? { - val cell = _cellRegistry.findNearest(position) ?: return null - val entryIDs = _cellRegistry.getEntryIDsByHexCell(cell)?.let { entryIDs -> - entryIDs.sortedBy { entryId -> - _entries[entryId]?.let { entry -> - haversineDistance(position, entry.point) + val cell = cellRegistry.findNearest(position) ?: return null + val entryIDs = + cellRegistry.getEntryIDsByHexCell(cell)?.let { entryIDs -> + entryIDs.sortedBy { entryId -> + entries[entryId]?.let { entry -> + haversineDistance(position, entry.point) + } } - } - } ?: return null + } ?: return null val entryId = entryIDs[0] - return _entries[entryId] + return entries[entryId] } - fun findByIdPrefix(prefix: String): List =_cellRegistry.findByIdPrefix(prefix) + fun findByIdPrefix(prefix: String): List = cellRegistry.findByIdPrefix(prefix) - fun registerEntry(entry: MarkerEntry, marker: ActualMarker) { - _markers[entry.id] = marker - _entries[entry.id] = entry - _cellRegistry.setPoint(entry) + fun registerEntry( + entry: MarkerEntry, + marker: ActualMarker, + ) { + markers[entry.id] = marker + entries[entry.id] = entry + cellRegistry.setPoint(entry) } fun updateEntry(entry: MarkerEntry) { - _entries[entry.id] = entry - _cellRegistry.setPoint(entry) + entries[entry.id] = entry + cellRegistry.setPoint(entry) } fun forEach(action: (MarkerEntry, ActualMarker) -> Unit) { - _markers.keys.forEach { key -> - action(_entries[key]!!, _markers[key]!!) + markers.keys.forEach { key -> + action(entries[key]!!, markers[key]!!) } } fun clear() { - _markers.clear() - _entries.clear() - _cellRegistry.clear() + markers.clear() + entries.clear() + cellRegistry.clear() } - fun getBitmapIcon(icon: MarkerIconProp) : BitmapIcon { + fun getBitmapIcon(icon: MarkerIconProp): BitmapIcon { val key = icon.hashCode() val cache = bitmapCache.get(key) if (cache != null) return cache - val iconBitmap = createDefaultMarkerShape( - fillColor = icon.fillColor, - strokeColor = icon.strokeColor, - strokeWidth = icon.strokeWidth, - scale = icon.scale, - label = icon.label, - labelTextColor = icon.labelTextColor, - labelTextSizeLogical = icon.labelTextSizeLogical, - fillDrawable = icon.fillDrawable, - iconDrawable = icon.iconDrawable, - ) + val iconBitmap = + createDefaultMarkerShape( + fillColor = icon.fillColor, + strokeColor = icon.strokeColor, + strokeWidth = icon.strokeWidth, + scale = icon.scale, + label = icon.label, + labelTextColor = icon.labelTextColor, + labelTextSizeLogical = icon.labelTextSizeLogical, + fillDrawable = icon.fillDrawable, + iconDrawable = icon.iconDrawable, + ) bitmapCache.put(key, iconBitmap) return iconBitmap } @@ -141,22 +148,29 @@ class MarkerManager(geocell: HexGeocell) { canvasSize: Size, iconRect: RectF, bitmap: Bitmap, - fillColor: Int? = null + fillColor: Int? = null, ): Bitmap { - val canvasBitmap = createBitmap(canvasSize.width.toInt(), canvasSize.height.toInt()) Canvas(canvasBitmap).apply { if (fillColor != null) { - drawRect(Rect(0, 0, canvasSize.width.toInt(), canvasSize.height.toInt()), Paint().also { - it.color = fillColor - it.style = Paint.Style.FILL - }) + drawRect( + Rect(0, 0, canvasSize.width.toInt(), canvasSize.height.toInt()), + Paint().also { + it.color = fillColor + it.style = Paint.Style.FILL + }, + ) } - drawBitmap(bitmap, null, iconRect, Paint().also { - it.isAntiAlias = true - it.flags = Paint.FILTER_BITMAP_FLAG - }) + drawBitmap( + bitmap, + null, + iconRect, + Paint().also { + it.isAntiAlias = true + it.flags = Paint.FILTER_BITMAP_FLAG + }, + ) } return canvasBitmap @@ -187,200 +201,211 @@ class MarkerManager(geocell: HexGeocell) { val scaleX = width / pathCoordinateSystemWidth val scaleY = height / pathCoordinateSystemHeight - val scaleFactor = 28.0f / 24.0f val offsetX = 2.0f val offsetY = 2.0f - val strokePath = Path().apply { - - // --- 最初のサブパス (外側の形状) --- - // "m12 0" -> moveTo(12*s + offsetX, 0*s + offsetY) - moveTo(12f * scaleFactor + offsetX, 0f * scaleFactor + offsetY) // (16f, 2f) - - // "c-4.4183 2.3685e-15 -8 3.5817-8 8" - relative cubicTo (all params scaled) - rCubicTo( - -4.4183f * scaleFactor, - 2.3685e-15f * scaleFactor, - -8f * scaleFactor, - 3.5817f * scaleFactor, - -8f * scaleFactor, - 8f * scaleFactor - ) - // (-5.1546833f, 0f, -9.333333f, 4.17865f, -9.333333f, 9.333333f) - - // "0 1.421 0.3816 2.75 1.0312 3.906" - implicit relative cubicTo - rCubicTo( - 0f * scaleFactor, - 1.421f * scaleFactor, - 0.3816f * scaleFactor, - 2.75f * scaleFactor, - 1.0312f * scaleFactor, - 3.906f * scaleFactor - ) - // (0f, 1.6578333f, 0.4452f, 3.2083333f, 1.2030667f, 4.557f) - - // "0.1079 0.192 0.221 0.381 0.3438 0.563" - implicit relative cubicTo - rCubicTo( - 0.1079f * scaleFactor, - 0.192f * scaleFactor, - 0.221f * scaleFactor, - 0.381f * scaleFactor, - 0.3438f * scaleFactor, - 0.563f * scaleFactor - ) - // (0.12588333f, 0.224f, 0.25783333f, 0.4445f, 0.4011f, 0.6568333f) - - // "l6.625 11.531" - relative lineTo - rLineTo(6.625f * scaleFactor, 11.531f * scaleFactor) - // (7.7291665f, 13.452833f) - - // "6.625-11.531" - implicit relative lineTo - rLineTo(6.625f * scaleFactor, -11.531f * scaleFactor) - // (7.7291665f, -13.452833f) - - // "c0.102-0.151 0.19-0.311 0.281-0.469" - relative cubicTo - rCubicTo( - 0.102f * scaleFactor, - -0.151f * scaleFactor, - 0.19f * scaleFactor, - -0.311f * scaleFactor, - 0.281f * scaleFactor, - -0.469f * scaleFactor - ) - // (0.119f, -0.17616667f, 0.22166666f, -0.36283332f, 0.32783332f, -0.54716665f) - - // "l0.063-0.094" - relative lineTo - rLineTo(0.063f * scaleFactor, -0.094f * scaleFactor) - // (0.0735f, -0.10966667f) - - // "c0.649-1.156 1.031-2.485 1.031-3.906" - relative cubicTo - rCubicTo( - 0.649f * scaleFactor, - -1.156f * scaleFactor, - 1.031f * scaleFactor, - -2.485f * scaleFactor, - 1.031f * scaleFactor, - -3.906f * scaleFactor - ) - // (0.7571667f, -1.3486667f, 1.2028333f, -2.8991666f, 1.2028333f, -4.557f) - - // "0-4.4183-3.582-8-8-8" - implicit relative cubicTo - rCubicTo( - 0f * scaleFactor, - -4.4183f * scaleFactor, - -3.582f * scaleFactor, - -8f * scaleFactor, - -8f * scaleFactor, - -8f * scaleFactor - ) - // (0f, -5.1546833f, -4.179f, -9.333333f, -9.333333f, -9.333333f) - - // "z" - closePath - close() // 最初のサブパスを閉じる - } - - val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).also { - if (fillDrawable != null) { - val iconCanvasSize = Size( - (svgOriginalWidth * 8).toFloat(), - (svgOriginalHeight * 8).toFloat(), + val strokePath = + Path().apply { + // --- 最初のサブパス (外側の形状) --- + // "m12 0" -> moveTo(12*s + offsetX, 0*s + offsetY) + moveTo(12f * scaleFactor + offsetX, 0f * scaleFactor + offsetY) // (16f, 2f) + + // "c-4.4183 2.3685e-15 -8 3.5817-8 8" - relative cubicTo (all params scaled) + rCubicTo( + -4.4183f * scaleFactor, + 2.3685e-15f * scaleFactor, + -8f * scaleFactor, + 3.5817f * scaleFactor, + -8f * scaleFactor, + 8f * scaleFactor, + ) + // (-5.1546833f, 0f, -9.333333f, 4.17865f, -9.333333f, 9.333333f) + + // "0 1.421 0.3816 2.75 1.0312 3.906" - implicit relative cubicTo + rCubicTo( + 0f * scaleFactor, + 1.421f * scaleFactor, + 0.3816f * scaleFactor, + 2.75f * scaleFactor, + 1.0312f * scaleFactor, + 3.906f * scaleFactor, + ) + // (0f, 1.6578333f, 0.4452f, 3.2083333f, 1.2030667f, 4.557f) + + // "0.1079 0.192 0.221 0.381 0.3438 0.563" - implicit relative cubicTo + rCubicTo( + 0.1079f * scaleFactor, + 0.192f * scaleFactor, + 0.221f * scaleFactor, + 0.381f * scaleFactor, + 0.3438f * scaleFactor, + 0.563f * scaleFactor, + ) + // (0.12588333f, 0.224f, 0.25783333f, 0.4445f, 0.4011f, 0.6568333f) + + // "l6.625 11.531" - relative lineTo + rLineTo(6.625f * scaleFactor, 11.531f * scaleFactor) + // (7.7291665f, 13.452833f) + + // "6.625-11.531" - implicit relative lineTo + rLineTo(6.625f * scaleFactor, -11.531f * scaleFactor) + // (7.7291665f, -13.452833f) + + // "c0.102-0.151 0.19-0.311 0.281-0.469" - relative cubicTo + rCubicTo( + 0.102f * scaleFactor, + -0.151f * scaleFactor, + 0.19f * scaleFactor, + -0.311f * scaleFactor, + 0.281f * scaleFactor, + -0.469f * scaleFactor, ) - val iconBitmap = this.drawIcon( - canvasSize = iconCanvasSize, - iconRect = RectF( - iconCanvasSize.width * 0f, - 0f, - iconCanvasSize.width * 1f, - iconCanvasSize.height * 1f, - ), - bitmap = toBitmap( - fillDrawable, - iconCanvasSize.width.toInt(), - iconCanvasSize.height.toInt(), - ), - fillColor = fillColor ?: Color.RED, + // (0.119f, -0.17616667f, 0.22166666f, -0.36283332f, 0.32783332f, -0.54716665f) + + // "l0.063-0.094" - relative lineTo + rLineTo(0.063f * scaleFactor, -0.094f * scaleFactor) + // (0.0735f, -0.10966667f) + + // "c0.649-1.156 1.031-2.485 1.031-3.906" - relative cubicTo + rCubicTo( + 0.649f * scaleFactor, + -1.156f * scaleFactor, + 1.031f * scaleFactor, + -2.485f * scaleFactor, + 1.031f * scaleFactor, + -3.906f * scaleFactor, ) - val iconBitmapTileMode = Shader.TileMode.CLAMP - val bitmapShader = BitmapShader(iconBitmap, iconBitmapTileMode, iconBitmapTileMode) - - // BitmapShaderのローカルマトリックスを設定して、 - // ビットマップがパスの32x32論理領域を適切にカバーするようにスケーリングする - val shaderMatrix = Matrix() - val shaderScaleX = svgOriginalWidth.toFloat() / iconBitmap.width.toFloat() - val shaderScaleY = svgOriginalHeight.toFloat() / iconBitmap.height.toFloat() - shaderMatrix.setScale(shaderScaleX, shaderScaleY) - - // 丸い部分の中心の論理座標 (24x24系) - val centerXLogical = (pathCoordinateSystemWidth - svgOriginalWidth) * 0.5 - val centerYLogical = (pathCoordinateSystemHeight - svgOriginalHeight) * 0.5 - shaderMatrix.postTranslate(centerXLogical.toFloat(), centerYLogical.toFloat()) - bitmapShader.setLocalMatrix(shaderMatrix) - it.shader = bitmapShader + // (0.7571667f, -1.3486667f, 1.2028333f, -2.8991666f, 1.2028333f, -4.557f) + + // "0-4.4183-3.582-8-8-8" - implicit relative cubicTo + rCubicTo( + 0f * scaleFactor, + -4.4183f * scaleFactor, + -3.582f * scaleFactor, + -8f * scaleFactor, + -8f * scaleFactor, + -8f * scaleFactor, + ) + // (0f, -5.1546833f, -4.179f, -9.333333f, -9.333333f, -9.333333f) + + // "z" - closePath + close() // 最初のサブパスを閉じる } - it.style = Paint.Style.FILL - it.color = fillColor ?: Color.RED - } - val iconPaint = iconDrawable?.let { + val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).also { - it.strokeWidth = 0f - val iconCanvasSize = Size( - (svgOriginalWidth * 8).toFloat(), - (svgOriginalHeight * 8).toFloat(), - ) - val iconBitmap2 = this.drawIcon( - canvasSize = iconCanvasSize, - iconRect = RectF( - iconCanvasSize.width * 0.15f, - 0f, - iconCanvasSize.width * 0.85f, - iconCanvasSize.height * 0.7f, - ), - bitmap = toBitmap( - iconDrawable, - iconCanvasSize.width.toInt(), - iconCanvasSize.height.toInt(), - ), - ) - val iconBitmapTileMode = Shader.TileMode.CLAMP - val bitmapShader = BitmapShader(iconBitmap2, iconBitmapTileMode, iconBitmapTileMode) - - // BitmapShaderのローカルマトリックスを設定して、 - // ビットマップがパスの32x32論理領域を適切にカバーするようにスケーリングする - val shaderMatrix = Matrix() - val shaderScaleX = svgOriginalWidth.toFloat() / iconBitmap2.width.toFloat() - val shaderScaleY = svgOriginalHeight.toFloat() / iconBitmap2.height.toFloat() - shaderMatrix.setScale(shaderScaleX, shaderScaleY) - - // 丸い部分の中心の論理座標 (24x24系) - val centerXLogical = (pathCoordinateSystemWidth - svgOriginalWidth) * 0.5 - val centerYLogical = (pathCoordinateSystemHeight - svgOriginalHeight) * 0.35 - shaderMatrix.postTranslate(centerXLogical.toFloat(), centerYLogical.toFloat()) - bitmapShader.setLocalMatrix(shaderMatrix) - it.shader = bitmapShader + if (fillDrawable != null) { + val iconCanvasSize = + Size( + (svgOriginalWidth * 8).toFloat(), + (svgOriginalHeight * 8).toFloat(), + ) + val iconBitmap = + this.drawIcon( + canvasSize = iconCanvasSize, + iconRect = + RectF( + iconCanvasSize.width * 0f, + 0f, + iconCanvasSize.width * 1f, + iconCanvasSize.height * 1f, + ), + bitmap = + toBitmap( + fillDrawable, + iconCanvasSize.width.toInt(), + iconCanvasSize.height.toInt(), + ), + fillColor = fillColor ?: Color.RED, + ) + val iconBitmapTileMode = Shader.TileMode.CLAMP + val bitmapShader = BitmapShader(iconBitmap, iconBitmapTileMode, iconBitmapTileMode) + + // BitmapShaderのローカルマトリックスを設定して、 + // ビットマップがパスの32x32論理領域を適切にカバーするようにスケーリングする + val shaderMatrix = Matrix() + val shaderScaleX = svgOriginalWidth.toFloat() / iconBitmap.width.toFloat() + val shaderScaleY = svgOriginalHeight.toFloat() / iconBitmap.height.toFloat() + shaderMatrix.setScale(shaderScaleX, shaderScaleY) + + // 丸い部分の中心の論理座標 (24x24系) + val centerXLogical = (pathCoordinateSystemWidth - svgOriginalWidth) * 0.5 + val centerYLogical = (pathCoordinateSystemHeight - svgOriginalHeight) * 0.5 + shaderMatrix.postTranslate(centerXLogical.toFloat(), centerYLogical.toFloat()) + bitmapShader.setLocalMatrix(shaderMatrix) + it.shader = bitmapShader + } + it.style = Paint.Style.FILL + it.color = fillColor ?: Color.RED } - } - val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).also { - it.style = Paint.Style.STROKE - it.strokeWidth = strokeWidth ?: 0f // SVGでのstrokeWidthに相当 - it.color = strokeColor ?: Color.WHITE - } + val iconPaint = + iconDrawable?.let { + Paint(Paint.ANTI_ALIAS_FLAG).also { + it.strokeWidth = 0f + val iconCanvasSize = + Size( + (svgOriginalWidth * 8).toFloat(), + (svgOriginalHeight * 8).toFloat(), + ) + val iconBitmap2 = + this.drawIcon( + canvasSize = iconCanvasSize, + iconRect = + RectF( + iconCanvasSize.width * 0.15f, + 0f, + iconCanvasSize.width * 0.85f, + iconCanvasSize.height * 0.7f, + ), + bitmap = + toBitmap( + iconDrawable, + iconCanvasSize.width.toInt(), + iconCanvasSize.height.toInt(), + ), + ) + val iconBitmapTileMode = Shader.TileMode.CLAMP + val bitmapShader = BitmapShader(iconBitmap2, iconBitmapTileMode, iconBitmapTileMode) + + // BitmapShaderのローカルマトリックスを設定して、 + // ビットマップがパスの32x32論理領域を適切にカバーするようにスケーリングする + val shaderMatrix = Matrix() + val shaderScaleX = svgOriginalWidth.toFloat() / iconBitmap2.width.toFloat() + val shaderScaleY = svgOriginalHeight.toFloat() / iconBitmap2.height.toFloat() + shaderMatrix.setScale(shaderScaleX, shaderScaleY) + + // 丸い部分の中心の論理座標 (24x24系) + val centerXLogical = (pathCoordinateSystemWidth - svgOriginalWidth) * 0.5 + val centerYLogical = (pathCoordinateSystemHeight - svgOriginalHeight) * 0.35 + shaderMatrix.postTranslate(centerXLogical.toFloat(), centerYLogical.toFloat()) + bitmapShader.setLocalMatrix(shaderMatrix) + it.shader = bitmapShader + } + } - val shadowPaint = Paint().apply { - this.color = Color.argb(0.5f, 0.0f, 0.0f, 0.0f) - this.isAntiAlias = true - // BlurMaskFilterの半径はピクセル単位。論理半径をピクセルに変換。 - // scaleXとscaleYが異なる場合を考慮し、平均または主要な軸のスケールを使う。ここではscaleYを例に。 - val pixelBlurRadius = 0.5 - if (pixelBlurRadius > 0f) { // 半径0だとエラーになるため - this.maskFilter = BlurMaskFilter(pixelBlurRadius.toFloat(), BlurMaskFilter.Blur.OUTER) - } else { - // 半径が非常に小さい場合は、単純な色描画にフォールバック(または何もしない) - // ここでは何もしない例(maskFilterがnullのまま) + val strokePaint = + Paint(Paint.ANTI_ALIAS_FLAG).also { + it.style = Paint.Style.STROKE + it.strokeWidth = strokeWidth ?: 0f // SVGでのstrokeWidthに相当 + it.color = strokeColor ?: Color.WHITE + } + + val shadowPaint = + Paint().apply { + this.color = Color.argb(0.5f, 0.0f, 0.0f, 0.0f) + this.isAntiAlias = true + // BlurMaskFilterの半径はピクセル単位。論理半径をピクセルに変換。 + // scaleXとscaleYが異なる場合を考慮し、平均または主要な軸のスケールを使う。ここではscaleYを例に。 + val pixelBlurRadius = 0.5 + if (pixelBlurRadius > 0f) { // 半径0だとエラーになるため + this.maskFilter = BlurMaskFilter(pixelBlurRadius.toFloat(), BlurMaskFilter.Blur.OUTER) + } else { + // 半径が非常に小さい場合は、単純な色描画にフォールバック(または何もしない) + // ここでは何もしない例(maskFilterがnullのまま) + } } - } canvas.withScale(scaleX.toFloat(), scaleY.toFloat()) { drawPath(strokePath, shadowPaint) @@ -389,17 +414,17 @@ class MarkerManager(geocell: HexGeocell) { drawPath(strokePath, it) } - // --- 3. ラベルの描画 (labelが指定されている場合) --- if (label != null) { - val textPaint = Paint().apply { - this.color = labelTextColor ?: Color.BLACK - this.textSize = labelTextSizeLogical ?: 10f // 論理サイズ。Canvasスケールで実際の大きさが決まる - this.textAlign = Paint.Align.CENTER - this.typeface = Typeface.DEFAULT_BOLD - this.isAntiAlias = true - this.isSubpixelText = true // より滑らかなテキスト描画のため - } + val textPaint = + Paint().apply { + this.color = labelTextColor ?: Color.BLACK + this.textSize = labelTextSizeLogical ?: 10f // 論理サイズ。Canvasスケールで実際の大きさが決まる + this.textAlign = Paint.Align.CENTER + this.typeface = Typeface.DEFAULT_BOLD + this.isAntiAlias = true + this.isSubpixelText = true // より滑らかなテキスト描画のため + } // 丸い部分の中心の論理座標 (32x32系) val centerXLogical = 16f @@ -417,15 +442,17 @@ class MarkerManager(geocell: HexGeocell) { } val visualNormalizedTipY = 0.9375f - val anchor = Offset( - x = 0.5, - y = visualNormalizedTipY + (0.5 / 64.0) - ) + val anchor = + Offset( + x = 0.5, + y = visualNormalizedTipY + (0.5 / 64.0), + ) - val size = Size( - width = width.toFloat(), - height = height.toFloat(), - ) + val size = + Size( + width = width.toFloat(), + height = height.toFloat(), + ) return BitmapIcon( bitmap = bitmap, @@ -434,8 +461,11 @@ class MarkerManager(geocell: HexGeocell) { ) } - private fun toBitmap(drawable: Drawable, width: Int, height: Int): Bitmap { - + private fun toBitmap( + drawable: Drawable, + width: Int, + height: Int, + ): Bitmap { return when (drawable) { is BitmapDrawable -> { // drawable.bitmap.asImageBitmap().asAndroidBitmap() @@ -450,5 +480,4 @@ class MarkerManager(geocell: HexGeocell) { } } } - } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MathExt.kt b/mapconductor-core/src/main/java/com/mapconductor/core/MathExt.kt index dd304a6c..41188a74 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MathExt.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/MathExt.kt @@ -3,8 +3,7 @@ package com.mapconductor.core import java.math.BigDecimal import java.math.RoundingMode -internal fun Double.toFixed(decimals: Int = 0): String { - return BigDecimal(this) +internal fun Double.toFixed(decimals: Int = 0): String = + BigDecimal(this) .setScale(decimals, RoundingMode.DOWN) .toPlainString() -} \ No newline at end of file diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/Offset.kt b/mapconductor-core/src/main/java/com/mapconductor/core/Offset.kt index d3c9803d..0d7a8d70 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/Offset.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/Offset.kt @@ -8,4 +8,4 @@ data class Offset( data class Size( val width: Double, val height: Double, -) \ No newline at end of file +) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt index a5f7635f..26f69ab2 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/OverlayProvider.kt @@ -1,40 +1,39 @@ package com.mapconductor.core import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.collectAsState import com.mapconductor.core.controller.MapViewController -import kotlinx.coroutines.flow.MutableStateFlow - -data class OverlayProvider( - val compositionLocal: ProvidableCompositionLocal>>, - val stateFlow: MutableStateFlow>, -) +import com.mapconductor.core.map.MapOverlay +import com.mapconductor.core.map.MapOverlayRegistry + +// data class OverlayProvider( +// val compositionLocal: ProvidableCompositionLocal>>, +// val stateFlow: MutableStateFlow>, +// ) + +// @Composable +// fun ProvideOverlayLocals( +// providers: List>, +// content: @Composable () -> Unit, +// ) { +// val wrapped = +// providers.foldRight(content) { provider, acc -> +// @Suppress("UNCHECKED_CAST") +// { +// val local = provider.compositionLocal as ProvidableCompositionLocal>> +// val flow = provider.stateFlow as MutableStateFlow> +// CompositionLocalProvider(local provides flow) { +// acc() +// } +// } +// } +// +// wrapped() +// } @Composable -fun ProvideOverlayLocals( - providers: List>, - content: @Composable () -> Unit, -) { - val wrapped = providers.foldRight(content) { provider, acc -> - @Suppress("UNCHECKED_CAST") - { - val local = provider.compositionLocal as ProvidableCompositionLocal>> - val flow = provider.stateFlow as MutableStateFlow> - CompositionLocalProvider(local provides flow) { - acc() - } - } - } - - wrapped() -} - - -@Composable -fun collectAndRenderOverlays( +fun CollectAndRenderOverlays( map: T?, registry: MapOverlayRegistry, controller: MapViewController, @@ -51,4 +50,3 @@ fun collectAndRenderOverlays( } } } - diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/ResourceProvider.kt b/mapconductor-core/src/main/java/com/mapconductor/core/ResourceProvider.kt index 0fe55819..22d6725a 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/ResourceProvider.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/ResourceProvider.kt @@ -1,12 +1,12 @@ package com.mapconductor.core -import android.content.Context -import android.content.res.Resources -import android.util.LruCache import androidx.annotation.Keep import com.mapconductor.core.marker.BitmapIcon import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import android.content.Context +import android.content.res.Resources +import android.util.LruCache data class IconResource( val name: String, @@ -22,7 +22,11 @@ object ResourceProvider { val initialized = _initialized.asStateFlow() private lateinit var appContext: Context - val density = Resources.getSystem().displayMetrics.density.toDouble() + val density = + Resources + .getSystem() + .displayMetrics.density + .toDouble() fun init(context: Context) { appContext = context.applicationContext @@ -38,23 +42,26 @@ object ResourceProvider { // Cache bytes object : LruCache(cacheSize.toInt()) { - override fun sizeOf(key: Int, iconRes: BitmapIcon): Int { - return iconRes.bitmap.byteCount / 1024 - } + override fun sizeOf( + key: Int, + iconRes: BitmapIcon, + ): Int = iconRes.bitmap.byteCount / 1024 } } - val DEFAULT_MARKER = IconResource( - name = "DEFAULT_MARKER", - width = 42.0, - height = 42.0, - anchorX = 24.0, - anchorY = 42.0, - resourceId = R.drawable.default_marker, - ) + val DEFAULT_MARKER = + IconResource( + name = "DEFAULT_MARKER", + width = 42.0, + height = 42.0, + anchorX = 24.0, + anchorY = 42.0, + resourceId = R.drawable.default_marker, + ) - private val resourceIDs = hashMapOf( - DEFAULT_MARKER.name to DEFAULT_MARKER, - ) + private val resourceIDs = + hashMapOf( + DEFAULT_MARKER.name to DEFAULT_MARKER, + ) // fun getIconResourceWithBitmap(name: String): BitmapIcon? { // synchronized(bitmapCache) { @@ -92,4 +99,4 @@ object ResourceProvider { fun clearCache() { bitmapCache.evictAll() } -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/utils.kt b/mapconductor-core/src/main/java/com/mapconductor/core/Utils.kt similarity index 99% rename from mapconductor-core/src/main/java/com/mapconductor/core/utils.kt rename to mapconductor-core/src/main/java/com/mapconductor/core/Utils.kt index 9e9bb54a..38a7c926 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/utils.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/Utils.kt @@ -7,4 +7,3 @@ fun calculateZIndex(geoPointBase: GeoPoint): Double { // 同じ緯度内では西が上(前)に来る return (-geoPointBase.latitude * 1_000_000 - geoPointBase.longitude) } - diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/controller/BaseMapViewController.kt b/mapconductor-core/src/main/java/com/mapconductor/core/controller/BaseMapViewController.kt index 60582cd9..d7c6fe0c 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/controller/BaseMapViewController.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/controller/BaseMapViewController.kt @@ -1,10 +1,10 @@ package com.mapconductor.core.controller -import com.mapconductor.core.MapViewHolder import com.mapconductor.core.MarkerManager import com.mapconductor.core.Offset import com.mapconductor.core.features.IGeoPoint import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.marker.BitmapIcon import com.mapconductor.core.marker.MarkerEntry import com.mapconductor.core.projection.WebMercator @@ -16,8 +16,11 @@ import kotlinx.coroutines.withContext interface MapViewController { val holder: MapViewHolder<*, *> val coroutine: CoroutineScope - suspend fun addMarkers(markerList : List) + + suspend fun addMarkers(markerList: List) + suspend fun clearOverlays() + fun toScreenOffset(position: IGeoPoint): Offset? } @@ -25,15 +28,17 @@ interface MarkerAddParams { val entry: MarkerEntry val icon: BitmapIcon } -interface MarkerUpdateParams { + +interface MarkerUpdateParams { val entry: MarkerEntry val icon: BitmapIcon val marker: ActualMarker } class BaseMapViewController< - ActualMarker: Any, // Actual marker instance type - >( + // Actual marker instance type + ActualMarker : Any, +>( val geocell: HexGeocell = HexGeocell(WebMercator), val coroutine: CoroutineScope = CoroutineScope(Dispatchers.Default), val onMarkerRemove: (id: String, marker: ActualMarker) -> Unit, @@ -43,30 +48,31 @@ class BaseMapViewController< val markerManager: MarkerManager = MarkerManager(geocell) suspend fun addMarkers(markerList: List) { - val current = markerList.filter { !markerManager.containsKey(it.state.id) } if (current.size == 0) return val entriesSet = markerManager.getValueSet() val added = current - entriesSet - val removed = entriesSet - current - val updated = markerList - .filter { entry -> - if (!markerManager.containsKey(entry.state.id)) return@filter false - return@filter !markerManager.equalsValue(entry) - } + val removed = entriesSet - current + val updated = + markerList + .filter { entry -> + if (!markerManager.containsKey(entry.state.id)) return@filter false + return@filter !markerManager.equalsValue(entry) + } val defaultIcon = markerManager.createDefaultMarkerShape() // Remove markers if (removed.isNotEmpty()) { - val willRemoveMarkers: List> = removed - .map { removedEntry -> - val id = removedEntry.state.id - val marker = markerManager.getMarker(id) - markerManager.removeEntry(id) - return@map Pair(id, marker) - } + val willRemoveMarkers: List> = + removed + .map { removedEntry -> + val id = removedEntry.state.id + val marker = markerManager.getMarker(id) + markerManager.removeEntry(id) + return@map Pair(id, marker) + } coroutine.launch { willRemoveMarkers.forEach { pair -> @@ -77,18 +83,21 @@ class BaseMapViewController< // Add new markers if (added.isNotEmpty()) { - val willAdd: List = added.map { entry -> - val markerIcon = entry.state.icon?.let { - markerManager.getBitmapIcon(it) - } ?: defaultIcon - object : MarkerAddParams { - override val entry: MarkerEntry = entry - override val icon: BitmapIcon = markerIcon + val willAdd: List = + added.map { entry -> + val markerIcon = + entry.state.icon?.let { + markerManager.getBitmapIcon(it) + } ?: defaultIcon + object : MarkerAddParams { + override val entry: MarkerEntry = entry + override val icon: BitmapIcon = markerIcon + } + } + val actualMarkers: List = + withContext(coroutine.coroutineContext) { + return@withContext onMarkerAdd.invoke(willAdd) } - } - val actualMarkers: List = withContext(coroutine.coroutineContext) { - return@withContext onMarkerAdd.invoke(willAdd) - } actualMarkers.forEachIndexed { index, actualMarker -> actualMarker?.let { val entry = added[index] @@ -99,18 +108,20 @@ class BaseMapViewController< // Update changed markers if (updated.isNotEmpty()) { - val willUpdate: List> = updated.map { entry -> - val markerIcon = entry.state.icon?.let { - markerManager.getBitmapIcon(it) - } ?: defaultIcon - markerManager.updateEntry(entry) - val marker = markerManager.getMarker(entry.id)!! - object : MarkerUpdateParams { - override val entry: MarkerEntry = entry - override val icon: BitmapIcon = markerIcon - override val marker: ActualMarker = marker + val willUpdate: List> = + updated.map { entry -> + val markerIcon = + entry.state.icon?.let { + markerManager.getBitmapIcon(it) + } ?: defaultIcon + markerManager.updateEntry(entry) + val marker = markerManager.getMarker(entry.id)!! + object : MarkerUpdateParams { + override val entry: MarkerEntry = entry + override val icon: BitmapIcon = markerIcon + override val marker: ActualMarker = marker + } } - } withContext(coroutine.coroutineContext) { onMarkerChanged(willUpdate) @@ -118,7 +129,6 @@ class BaseMapViewController< } } - suspend fun clearOverlays() { withContext(coroutine.coroutineContext) { markerManager.forEach { entry, marker -> onMarkerRemove(entry.id, marker) } @@ -127,5 +137,4 @@ class BaseMapViewController< } fun getMarkerEntry(id: String): MarkerEntry? = markerManager.getEntry(id) - -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoPoint.kt b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoPoint.kt index 71f62740..b99d35db 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoPoint.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoPoint.kt @@ -22,10 +22,7 @@ class GeoPoint( override val longitude: Double, override val altitude: Double = 0.0, ) : IGeoPoint { - - fun toUrlValue(precision: Int = 6): String { - return "${latitude.toFixed(precision)},${longitude.toFixed(precision)}" - } + fun toUrlValue(precision: Int = 6): String = "${latitude.toFixed(precision)},${longitude.toFixed(precision)}" override fun equals(other: Any?): Boolean { if (this === other) return true @@ -33,8 +30,8 @@ class GeoPoint( val tolerance = 1e-7 return abs(latitude - other.latitude) < tolerance && - abs(longitude - other.longitude) < tolerance && - abs(altitude - other.altitude) < tolerance + abs(longitude - other.longitude) < tolerance && + abs(altitude - other.altitude) < tolerance } override fun hashCode(): Int { @@ -50,17 +47,25 @@ class GeoPoint( } companion object { - fun fromLatLong(latitude: Double, longitude: Double) = GeoPoint(latitude, longitude) - fun fromLongLat(longitude: Double, latitude: Double) = GeoPoint(latitude, longitude) - fun from(position: IGeoPoint) = when(position) { - is GeoPoint -> position - else -> GeoPoint( - latitude = position.latitude, - longitude = position.longitude, - altitude = position.altitude ?: 0.0, - ) - } - } -} + fun fromLatLong( + latitude: Double, + longitude: Double, + ) = GeoPoint(latitude, longitude) + fun fromLongLat( + longitude: Double, + latitude: Double, + ) = GeoPoint(latitude, longitude) + fun from(position: IGeoPoint) = + when (position) { + is GeoPoint -> position + else -> + GeoPoint( + latitude = position.latitude, + longitude = position.longitude, + altitude = position.altitude ?: 0.0, + ) + } + } +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt index 3c5fe8bc..4be64fab 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/features/GeoRectBounds.kt @@ -3,10 +3,9 @@ package com.mapconductor.core.features import kotlin.math.max import kotlin.math.min - data class GeoRectBounds( var southWest: GeoPoint? = null, - var northEast: GeoPoint? = null + var northEast: GeoPoint? = null, ) { val isEmpty: Boolean get() = southWest == null || northEast == null @@ -46,23 +45,32 @@ data class GeoRectBounds( northEast = GeoPoint(north, east) } - private fun distanceEast(lon1: Double, lon2: Double): Double { + private fun distanceEast( + lon1: Double, + lon2: Double, + ): Double { val d = (lon2 - lon1 + 360) % 360 return if (d <= 180) d else 360 - d } - private fun distanceWest(lon1: Double, lon2: Double): Double { + private fun distanceWest( + lon1: Double, + lon2: Double, + ): Double { val d = (lon1 - lon2 + 360) % 360 return if (d <= 180) d else 360 - d } - private fun containsLongitude(lon: Double, west: Double, east: Double): Boolean { - return if (west <= east) { + private fun containsLongitude( + lon: Double, + west: Double, + east: Double, + ): Boolean = + if (west <= east) { lon in west..east } else { lon >= west || lon <= east } - } fun contains(point: IGeoPoint): Boolean { if (isEmpty) return false @@ -87,12 +95,13 @@ data class GeoRectBounds( val lng1 = sw.longitude val lng2 = ne.longitude - val centerLng = if (lng1 <= lng2) { - (lng1 + lng2) / 2.0 - } else { - val mid = (lng1 + (lng2 + 360)) / 2.0 - if (mid > 180) mid - 360 else mid - } + val centerLng = + if (lng1 <= lng2) { + (lng1 + lng2) / 2.0 + } else { + val mid = (lng1 + (lng2 + 360)) / 2.0 + if (mid > 180) mid - 360 else mid + } return GeoPoint(centerLat, centerLng) } @@ -134,7 +143,7 @@ data class GeoRectBounds( sw.latitude.toFixed(precision), sw.longitude.toFixed(precision), ne.latitude.toFixed(precision), - ne.longitude.toFixed(precision) + ne.longitude.toFixed(precision), ).joinToString(",") } @@ -148,21 +157,19 @@ data class GeoRectBounds( val latOverlap = sw1.latitude <= ne2.latitude && ne1.latitude >= sw2.latitude - val lngOverlap = containsLongitude(sw2.longitude, sw1.longitude, ne1.longitude) || + val lngOverlap = + containsLongitude(sw2.longitude, sw1.longitude, ne1.longitude) || containsLongitude(ne2.longitude, sw1.longitude, ne1.longitude) return latOverlap && lngOverlap } - override fun toString(): String { - return if (isEmpty) { + override fun toString(): String = + if (isEmpty) { "((1, 180), (-1, -180))" } else { "((${southWest!!.latitude}, ${southWest!!.longitude}), (${northEast!!.latitude}, ${northEast!!.longitude}))" } - } - fun equalsTo(other: GeoRectBounds): Boolean { - return this.southWest == other.southWest && this.northEast == other.northEast - } + fun equalsTo(other: GeoRectBounds): Boolean = this.southWest == other.southWest && this.northEast == other.northEast } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexCellRegistry.kt b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexCellRegistry.kt index 873ac1b6..42febb24 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexCellRegistry.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexCellRegistry.kt @@ -7,7 +7,10 @@ import kotlin.math.sqrt // HexCellRegistry(KDTree + markDirty制御) -class HexCellRegistry(private val geocell: HexGeocell, private val zoom: Double) { +class HexCellRegistry( + private val geocell: HexGeocell, + private val zoom: Double, +) { private var kdTree: KDTree? = null private val allCells = ConcurrentHashMap() private val entryIDsByCell = ConcurrentHashMap>() @@ -53,7 +56,7 @@ class HexCellRegistry(private val geocell: HexGeocell, priv allCells[cellId] = cell allEntries[entry.id] = cell.id - entryIDsByCell[cellId] = (entryIDsByCell[cellId] ?: emptyList()) + entry.id + entryIDsByCell[cellId] = (entryIDsByCell[cellId] ?: emptyList()) + entry.id markDirty() return cell } @@ -86,7 +89,9 @@ class HexCellRegistry(private val geocell: HexGeocell, priv kdTree = null } - fun markDirty() { needsRebuild = true } + fun markDirty() { + needsRebuild = true + } private fun rebuildIfNeeded() { if (needsRebuild) { @@ -105,12 +110,18 @@ class HexCellRegistry(private val geocell: HexGeocell, priv return kdTree?.nearestWithDistance(geocell.projection.project(point)) } - fun findNearestKWithDistance(point: IGeoPoint, k: Int): List { + fun findNearestKWithDistance( + point: IGeoPoint, + k: Int, + ): List { rebuildIfNeeded() return kdTree?.nearestKWithDistance(geocell.projection.project(point), k).orEmpty() } - fun findWithinRadiusWithDistance(point: IGeoPoint, radius: Double): List { + fun findWithinRadiusWithDistance( + point: IGeoPoint, + radius: Double, + ): List { rebuildIfNeeded() return kdTree?.withinRadiusWithDistance(geocell.projection.project(point), radius).orEmpty() } @@ -123,20 +134,23 @@ class HexCellRegistry(private val geocell: HexGeocell, priv position: IGeoPoint, zoom: Double, pixels: Double, - tileSize: Int = 256 + tileSize: Int = 256, ): Double { // val scale = 1.0 / (2.0.pow(zoom)) // ピクセルの地図上のスケール val deltaLng = 360.0 * pixels / (tileSize * 2.0.pow(zoom)) // 経度方向にずらす val p1 = geocell.projection.project(position) - val p2 = geocell.projection.project(object : IGeoPoint { - override val latitude - get() = position.latitude - override val longitude: Double - get() = position.longitude + deltaLng - override val altitude: Double? - get() = position.altitude - }) + val p2 = + geocell.projection.project( + object : IGeoPoint { + override val latitude + get() = position.latitude + override val longitude: Double + get() = position.longitude + deltaLng + override val altitude: Double? + get() = position.altitude + }, + ) val dx = p2.x - p1.x val dy = p2.y - p1.y @@ -147,13 +161,11 @@ class HexCellRegistry(private val geocell: HexGeocell, priv position: IGeoPoint, zoom: Double, pixels: Double, - tileSize: Int = 256 + tileSize: Int = 256, ): List { val meters = metersPerPixel(position, zoom, pixels, tileSize) return findWithinRadiusWithDistance(position, meters) } - fun findByIdPrefix(prefix: String): List { - return all().filter { it.id.startsWith(prefix) } - } + fun findByIdPrefix(prefix: String): List = all().filter { it.id.startsWith(prefix) } } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexGeocell.kt b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexGeocell.kt index 5226044e..f7d59de5 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexGeocell.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/HexGeocell.kt @@ -11,18 +11,24 @@ import kotlin.math.roundToInt import kotlin.math.sin import kotlin.math.sqrt -data class HexCoord(val q: Int, val r: Int, val depth: Int = 0) { - override fun toString(): String { - return "H${q}_${r}_${depth}" - } +data class HexCoord( + val q: Int, + val r: Int, + val depth: Int = 0, +) { + override fun toString(): String = "H${q}_${r}_$depth" } -enum class Direction6(val dq: Int, val dr: Int) { + +enum class Direction6( + val dq: Int, + val dr: Int, +) { Right(1, 0), RightUp(1, -1), LeftUp(0, -1), Left(-1, 0), LeftDown(-1, 1), - RightDown(0, 1) + RightDown(0, 1), } interface IdentifiedPoint { @@ -34,19 +40,16 @@ data class HexCell( val coord: HexCoord, val centerLatLng: IGeoPoint, val centerXY: Offset, - val id: String + val id: String, ) { - fun idPrefix(levels: Int): String { - return id.split("_").take(levels + 1).joinToString("_") - } + fun idPrefix(levels: Int): String = id.split("_").take(levels + 1).joinToString("_") } data class HexCellWithDistance( val cell: HexCell, - val distanceMeters: Double + val distanceMeters: Double, ) - class HexGeocell( val projection: Projection, // The radius in meter when zoom level is zero. @@ -56,15 +59,19 @@ class HexGeocell( // - Use the value from 1000000 to 100000 for low zoom level (6 - 9) val baseHexSize: Int = 10000, ) { - - fun latLngToHexCoord(position: IGeoPoint, zoom: Double): HexCoord { + fun latLngToHexCoord( + position: IGeoPoint, + zoom: Double, + ): HexCoord { val hexSize = adjustedHexSize(position.latitude, zoom) val offset = projection.project(position) return pixelToHex(offset, hexSize) } - - fun latLngToHexCell(position: IGeoPoint, zoom: Double): HexCell { + fun latLngToHexCell( + position: IGeoPoint, + zoom: Double, + ): HexCell { val coord = latLngToHexCoord(position, zoom) val id = hexToCellId(coord) val centerLatLng = hexToLatLngCenter(coord, position.latitude, zoom) @@ -72,7 +79,11 @@ class HexGeocell( return HexCell(coord, centerLatLng, centerXY, id) } - fun hexToLatLngCenter(coord: HexCoord, latHint: Double, zoom: Double): IGeoPoint { + fun hexToLatLngCenter( + coord: HexCoord, + latHint: Double, + zoom: Double, + ): IGeoPoint { val hexSize = adjustedHexSize(latHint, zoom) val center = hexCenterXY(coord, hexSize) return projection.unproject(center) @@ -80,7 +91,11 @@ class HexGeocell( fun hexToCellId(coord: HexCoord): String = "H${coord.q}_${coord.r}" - fun hexToPolygonLatLng(coord: HexCoord, latHint: Double, zoom: Double): List { + fun hexToPolygonLatLng( + coord: HexCoord, + latHint: Double, + zoom: Double, + ): List { val hexSize = adjustedHexSize(latHint, zoom) val center = hexCenterXY(coord, hexSize) return (0 until 6).map { i -> @@ -91,7 +106,10 @@ class HexGeocell( } } - fun enclosingCellOf(points: List, zoom: Double): HexCell { + fun enclosingCellOf( + points: List, + zoom: Double, + ): HexCell { val center = computeCentroid(points.map { it.point }) val coord = latLngToHexCoord(center, zoom) val centerLatLng = hexToLatLngCenter(coord, center.latitude, zoom) @@ -100,46 +118,61 @@ class HexGeocell( return HexCell(coord, centerLatLng, centerXY, id) } - fun hexCellsForPointsWithId(points: List, zoom: Double): Set { - return points.map { - val coord = latLngToHexCoord(it.point, zoom) - val centerLatLng = hexToLatLngCenter(coord, it.point.latitude, zoom) - val centerXY = projection.project(centerLatLng) - val cellId = hexToCellId(coord) - val cell = HexCell(coord, centerLatLng, centerXY, cellId) - IdentifiedHexCell(it.id, cell) - }.toSet() - } + fun hexCellsForPointsWithId( + points: List, + zoom: Double, + ): Set = + points + .map { + val coord = latLngToHexCoord(it.point, zoom) + val centerLatLng = hexToLatLngCenter(coord, it.point.latitude, zoom) + val centerXY = projection.project(centerLatLng) + val cellId = hexToCellId(coord) + val cell = HexCell(coord, centerLatLng, centerXY, cellId) + IdentifiedHexCell(it.id, cell) + }.toSet() private fun computeCentroid(points: List): IGeoPoint { val avgLat = points.map { it.latitude }.average() val avgLng = points.map { it.longitude }.average() - return object : IGeoPoint{ + return object : IGeoPoint { override val latitude: Double = avgLat override val longitude: Double = avgLng override val altitude: Double? = null } } - private fun adjustedHexSize(lat: Double, zoom: Double): Double { + private fun adjustedHexSize( + lat: Double, + zoom: Double, + ): Double { val scale = 1.0 / (2.0.pow(zoom)) val latScale = cos(lat * PI / 180).coerceAtLeast(0.01) return baseHexSize * scale / latScale } - private fun hexCenterXY(coord: HexCoord, hexSize: Double): Offset { + private fun hexCenterXY( + coord: HexCoord, + hexSize: Double, + ): Offset { val x = hexSize * (3.0 / 2.0 * coord.q) val y = hexSize * (sqrt(3.0) * (coord.r + coord.q / 2.0)) return Offset(x, y) } - private fun pixelToHex(offset: Offset, hexSize: Double): HexCoord { + private fun pixelToHex( + offset: Offset, + hexSize: Double, + ): HexCoord { val q = (2.0 / 3.0 * offset.x / hexSize) val r = (-1.0 / 3.0 * offset.x + sqrt(3.0) / 3.0 * offset.y) / hexSize return cubeRound(q, r) } - private fun cubeRound(q: Double, r: Double): HexCoord { + private fun cubeRound( + q: Double, + r: Double, + ): HexCoord { val x = q val y = r val z = -x - y @@ -152,9 +185,13 @@ class HexGeocell( val dy = abs(ry - y) val dz = abs(rz - z) - if (dx > dy && dx > dz) rx = -ry - rz - else if (dy > dz) ry = -rx - rz - else rz = -rx - ry + if (dx > dy && dx > dz) { + rx = -ry - rz + } else if (dy > dz) { + ry = -rx - rz + } else { + rz = -rx - ry + } return HexCoord(rx, ry) } @@ -162,5 +199,5 @@ class HexGeocell( data class IdentifiedHexCell( val id: String, - val cell: HexCell + val cell: HexCell, ) diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt index 39fbbcf9..fb5078d9 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/geocell/KDTree.kt @@ -7,22 +7,45 @@ import kotlin.math.sqrt // KDTree 本体(nearest, k-NN, radius検索対応) -class KDTree(private val points: List) { +class KDTree( + private val points: List, +) { private val root = build(points, 0) - private class Node(val cell: HexCell, val left: Node?, val right: Node?, val axis: Int) - - private fun build(items: List, depth: Int): Node? { + private class Node( + val cell: HexCell, + val left: Node?, + val right: Node?, + val axis: Int, + ) + + private fun build( + items: List, + depth: Int, + ): Node? { if (items.isEmpty()) return null val axis = depth % 2 val sorted = items.sortedBy { if (axis == 0) it.centerXY.x else it.centerXY.y } val mid = sorted.size / 2 - return Node(sorted[mid], build(sorted.subList(0, mid), depth + 1), build(sorted.subList(mid + 1, sorted.size), depth + 1), axis) + return Node( + sorted[mid], + build(sorted.subList(0, mid), depth + 1), + build( + sorted.subList(mid + 1, sorted.size), + depth + 1, + ), + axis, + ) } fun nearest(query: Offset): HexCell? = nearest(root, query, null, Double.MAX_VALUE) - private fun nearest(node: Node?, query: Offset, best: HexCell?, bestDist: Double): HexCell? { + private fun nearest( + node: Node?, + query: Offset, + best: HexCell?, + bestDist: Double, + ): HexCell? { if (node == null) return best val axis = node.axis val queryVal = if (axis == 0) query.x else query.y @@ -50,13 +73,21 @@ class KDTree(private val points: List) { return HexCellWithDistance(cell, distanceMeters(query, cell.centerXY)) } - fun nearestKWithDistance(query: Offset, k: Int): List { + fun nearestKWithDistance( + query: Offset, + k: Int, + ): List { val queue = PriorityQueue>(compareByDescending { it.first }) nearestK(root, query, k, queue) return queue.map { HexCellWithDistance(it.second, sqrt(it.first)) } } - private fun nearestK(node: Node?, query: Offset, k: Int, queue: PriorityQueue>) { + private fun nearestK( + node: Node?, + query: Offset, + k: Int, + queue: PriorityQueue>, + ) { if (node == null) return val distSq = squaredDistance(query, node.cell.centerXY) if (queue.size < k) { @@ -76,14 +107,22 @@ class KDTree(private val points: List) { } } - fun withinRadiusWithDistance(query: Offset, radius: Double): List { + fun withinRadiusWithDistance( + query: Offset, + radius: Double, + ): List { val radiusSq = radius * radius val result = mutableListOf() withinRadius(root, query, radiusSq, result) return result } - private fun withinRadius(node: Node?, query: Offset, radiusSq: Double, result: MutableList) { + private fun withinRadius( + node: Node?, + query: Offset, + radiusSq: Double, + result: MutableList, + ) { if (node == null) return val distSq = squaredDistance(query, node.cell.centerXY) if (distSq <= radiusSq) { @@ -100,11 +139,17 @@ class KDTree(private val points: List) { } } - private fun squaredDistance(a: Offset, b: Offset): Double { + private fun squaredDistance( + a: Offset, + b: Offset, + ): Double { val dx = a.x - b.x val dy = a.y - b.y return dx * dx + dy * dy } - private fun distanceMeters(a: Offset, b: Offset): Double = sqrt(squaredDistance(a, b)) + private fun distanceMeters( + a: Offset, + b: Offset, + ): Double = sqrt(squaredDistance(a, b)) } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/info/DrawInfoBubble.kt b/mapconductor-core/src/main/java/com/mapconductor/core/info/DrawInfoBubble.kt index 3294a2af..fa15b62a 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/info/DrawInfoBubble.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/info/DrawInfoBubble.kt @@ -24,7 +24,7 @@ internal fun DrawInfoBubble( contentPadding: Dp, cornerRadius: Dp, tailSize: Dp, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { Box(modifier = modifier) { Canvas(modifier = Modifier.matchParentSize()) { @@ -33,75 +33,79 @@ internal fun DrawInfoBubble( val tailSizePx = tailSize.toPx() val cornerPx = cornerRadius.toPx() - val path = Path().apply { - moveTo(2 * cornerPx, 0f) - lineTo(width - 2 * cornerPx, 0f) - // -- top / right corner -- - arcTo( - rect = Rect( - topLeft = Offset(width - 2 * cornerPx, 0f), - bottomRight = Offset(width, 2 * cornerPx), - ), - startAngleDegrees = -90f, - sweepAngleDegrees = 90f, - forceMoveTo = false, - ) - lineTo(width, height - tailSizePx - 2 * cornerPx) - // -- bottom / right corner -- - arcTo( - rect = Rect( - topLeft = Offset(width - 2 * cornerPx, height - tailSizePx - 2 * cornerPx), - bottomRight = Offset(width, height - tailSizePx), - ), - startAngleDegrees = 0f, - sweepAngleDegrees = 90f, - forceMoveTo = false - ) - // -- tail -- - lineTo(width / 2 + tailSizePx / 2, height - tailSizePx) - lineTo(width / 2, height) - lineTo(width / 2 - tailSizePx / 2, height - tailSizePx) - lineTo(2 * cornerPx, height - tailSizePx) - // -- bottom / left - arcTo( - rect = Rect( - topLeft = Offset(0f, height - tailSizePx - 2 * cornerPx), - bottomRight = Offset(2 * cornerPx, height - tailSizePx), - ), - startAngleDegrees = 90f, - sweepAngleDegrees = 90f, - forceMoveTo = false - ) - lineTo(0f, 2 * cornerPx) - arcTo( - rect = Rect( - topLeft = Offset(0f, 0f), - bottomRight = Offset(2 * cornerPx, 2 * cornerPx), - ), - startAngleDegrees = 180f, - sweepAngleDegrees = 90f, - forceMoveTo = false - ) - close() - } + val path = + Path().apply { + moveTo(2 * cornerPx, 0f) + lineTo(width - 2 * cornerPx, 0f) + // -- top / right corner -- + arcTo( + rect = + Rect( + topLeft = Offset(width - 2 * cornerPx, 0f), + bottomRight = Offset(width, 2 * cornerPx), + ), + startAngleDegrees = -90f, + sweepAngleDegrees = 90f, + forceMoveTo = false, + ) + lineTo(width, height - tailSizePx - 2 * cornerPx) + // -- bottom / right corner -- + arcTo( + rect = + Rect( + topLeft = Offset(width - 2 * cornerPx, height - tailSizePx - 2 * cornerPx), + bottomRight = Offset(width, height - tailSizePx), + ), + startAngleDegrees = 0f, + sweepAngleDegrees = 90f, + forceMoveTo = false, + ) + // -- tail -- + lineTo(width / 2 + tailSizePx / 2, height - tailSizePx) + lineTo(width / 2, height) + lineTo(width / 2 - tailSizePx / 2, height - tailSizePx) + lineTo(2 * cornerPx, height - tailSizePx) + // -- bottom / left + arcTo( + rect = + Rect( + topLeft = Offset(0f, height - tailSizePx - 2 * cornerPx), + bottomRight = Offset(2 * cornerPx, height - tailSizePx), + ), + startAngleDegrees = 90f, + sweepAngleDegrees = 90f, + forceMoveTo = false, + ) + lineTo(0f, 2 * cornerPx) + arcTo( + rect = + Rect( + topLeft = Offset(0f, 0f), + bottomRight = Offset(2 * cornerPx, 2 * cornerPx), + ), + startAngleDegrees = 180f, + sweepAngleDegrees = 90f, + forceMoveTo = false, + ) + close() + } drawPath(path, color = bubbleColor, style = Fill) drawPath(path, color = borderColor, style = Stroke(width = 2f)) } // 内容 Box( - modifier = Modifier - .padding( - start = contentPadding, - top = contentPadding, - bottom = contentPadding + tailSize, - end = contentPadding, - ) - .wrapContentSize() - .clip(RoundedCornerShape(cornerRadius)) + modifier = + Modifier + .padding( + start = contentPadding, + top = contentPadding, + bottom = contentPadding + tailSize, + end = contentPadding, + ).wrapContentSize() + .clip(RoundedCornerShape(cornerRadius)), ) { content() } } - -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoBubbleCompose.kt b/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoBubbleCompose.kt index d52f59f4..740a8f6e 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoBubbleCompose.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoBubbleCompose.kt @@ -11,7 +11,6 @@ import androidx.compose.ui.unit.dp import com.mapconductor.core.marker.MarkerScope import com.mapconductor.core.marker.MarkerState - @Composable fun MarkerScope.InfoAnchor( props: MarkerState, @@ -35,7 +34,7 @@ fun MarkerScope.InfoBubble( contentPadding: Dp = 16.dp, cornerRadius: Dp = 8.dp, tailSize: Dp = 16.dp, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { InfoAnchor( props = markerState, diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoWindowOverlay.kt b/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoWindowOverlay.kt index 72d9396c..c956b0be 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoWindowOverlay.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/info/InfoWindowOverlay.kt @@ -21,21 +21,21 @@ internal fun InfoWindowCompose( centerOffset: Offset, screenOffset: Offset, anchor: Offset, - content: @Composable () -> Unit + content: @Composable () -> Unit, ) { var size by remember { mutableStateOf(IntSize.Zero) } Box( - modifier = Modifier - .onGloballyPositioned { - size = it.size - } - .offset { - IntOffset( - (screenOffset.x - (anchor.x - 0.5) * size.width - centerOffset.x).toInt(), - (screenOffset.y - (anchor.y - 0.5) * size.height - centerOffset.y).toInt(), - ) - }, + modifier = + Modifier + .onGloballyPositioned { + size = it.size + }.offset { + IntOffset( + (screenOffset.x - (anchor.x - 0.5) * size.width - centerOffset.x).toInt(), + (screenOffset.y - (anchor.y - 0.5) * size.height - centerOffset.y).toInt(), + ) + }, ) { content() } @@ -47,6 +47,7 @@ data class InfoBubbleSpec( val content: @Composable () -> Unit, ) -val LocalInfoBubbleCollector = compositionLocalOf>> { - error("InfoBubble must be under ") -} \ No newline at end of file +val LocalInfoBubbleCollector = + compositionLocalOf>> { + error("InfoBubble must be under ") + } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapCameraPositionBase.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapCameraPositionBase.kt new file mode 100644 index 00000000..71832f11 --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapCameraPositionBase.kt @@ -0,0 +1,36 @@ +package com.mapconductor.core.map + +import com.mapconductor.core.features.IGeoPoint + +interface IMapCameraPosition { + val position: IGeoPoint + val zoom: Double + val bearing: Double + val tilt: Double + val paddings: MapPaddings? +} + +open class MapCameraPositionBase( + override val position: IGeoPoint, + override val zoom: Double = 0.0, + override val bearing: Double = 0.0, + override val tilt: Double = 0.0, + override val paddings: MapPaddings? = MapPaddingsImpl.Companion.Zeros, +) : IMapCameraPosition { + companion object { + val Default = + MapCameraPositionBase( + position = + object : IGeoPoint { + override val latitude: Double = 0.0 + override val longitude: Double = 0.0 + override val altitude: Double? = null + + fun toUrlValue(precision: Int): String = "0.0,0.0" + }, + zoom = 0.0, + bearing = 0.0, + tilt = 0.0, + ) + } +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MapStyle.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapDesignType.kt similarity index 64% rename from mapconductor-core/src/main/java/com/mapconductor/core/MapStyle.kt rename to mapconductor-core/src/main/java/com/mapconductor/core/map/MapDesignType.kt index 644795cf..6aaf6d74 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MapStyle.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapDesignType.kt @@ -1,6 +1,7 @@ -package com.mapconductor.core +package com.mapconductor.core.map interface MapDesignType { val id: T + fun getValue(): T -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapPaddingsImpl.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapPaddingsImpl.kt new file mode 100644 index 00000000..cc82ed43 --- /dev/null +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapPaddingsImpl.kt @@ -0,0 +1,33 @@ +package com.mapconductor.core.map + +interface MapPaddings { + val top: Double + val left: Double + val bottom: Double + val right: Double +} + +open class MapPaddingsImpl + @JvmOverloads + constructor( + override val top: Double = 0.0, + override val left: Double = 0.0, + override val bottom: Double = 0.0, + override val right: Double = 0.0, + ) : MapPaddings { + companion object { + val Zeros: MapPaddingsImpl = MapPaddingsImpl(0.0, 0.0, 0.0, 0.0) + + fun from(paddingsImpl: MapPaddings) = + when (paddingsImpl) { + is MapPaddingsImpl -> paddingsImpl + else -> + MapPaddingsImpl( + top = paddingsImpl.top, + left = paddingsImpl.left, + bottom = paddingsImpl.bottom, + right = paddingsImpl.right, + ) + } + } + } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt index e148e96c..5383a338 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewBase.kt @@ -1,7 +1,5 @@ package com.mapconductor.core.map -import android.view.View -import android.view.ViewGroup import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -23,28 +21,29 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView -import com.mapconductor.core.MapOverlayRegistry -import com.mapconductor.core.MapViewHolder +import com.mapconductor.core.CollectAndRenderOverlays import com.mapconductor.core.ResourceProvider -import com.mapconductor.core.collectAndRenderOverlays import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.info.InfoBubbleSpec import com.mapconductor.core.info.LocalInfoBubbleCollector import com.mapconductor.core.marker.LocalMarkerCollector import kotlinx.coroutines.flow.MutableStateFlow +import android.view.View +import android.view.ViewGroup @Composable fun < - SpecificState : MapViewState<*>, - SpecificController : MapViewController, // Replace Any with a base MapViewController if you have one - // Generic type for the actual Android Map View (e.g., com.google.android.gms.maps.MapView) - ActualMapView : View, - // Generic type for the actual Map SDK object (e.g., GoogleMap, HereMapSDK.MapController) - ActualMap : Any, - // SpecificViewHolder is now constrained by your MapViewHolder interface - // and uses the ActualMapView and ActualMap generic types. - SpecificViewHolder : MapViewHolder, - SpecificScope : MapViewScope + SpecificState : MapViewState<*>, + // Replace Any with a base MapViewController if you have one + // Generic type for the actual Android Map View (e.g., com.google.android.gms.maps.MapView) + SpecificController : MapViewController, + ActualMapView : View, + // Generic type for the actual Map SDK object (e.g., GoogleMap, HereMapSDK.MapController) + ActualMap : Any, + // SpecificViewHolder is now constrained by your MapViewHolder interface + // and uses the ActualMapView and ActualMap generic types. + SpecificViewHolder : MapViewHolder, + SpecificScope : MapViewScope, > MapViewBase( state: SpecificState, modifier: Modifier = Modifier, @@ -77,7 +76,7 @@ fun < controllerRef.value?.let { controller -> holderRef.value?.let { holder -> mapProvider(holder)?.let { map -> - collectAndRenderOverlays( + CollectAndRenderOverlays( map = map, registry = registry, // This should come from the specific scope or be passed controller = controller, @@ -88,10 +87,11 @@ fun < } Box( - modifier = modifier - .background(color = Color.LightGray) - .fillMaxSize() - .clipToBounds(), + modifier = + modifier + .background(color = Color.LightGray) + .fillMaxSize() + .clipToBounds(), contentAlignment = Alignment.Center, ) { when (initState) { @@ -99,10 +99,11 @@ fun < BasicText( text = "Not initialized yet", modifier = Modifier.fillMaxWidth(), - style = TextStyle.Default.merge( - fontSize = 13.sp, - textAlign = TextAlign.Center, - ), + style = + TextStyle.Default.merge( + fontSize = 13.sp, + textAlign = TextAlign.Center, + ), ) } InitState.Failed -> { @@ -125,10 +126,11 @@ fun < } else { AndroidView(factory = { _ -> val view = viewProvider(holderRef.value!!) - (view as ViewGroup).layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ) + (view as ViewGroup).layoutParams = + ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) view }) } @@ -153,7 +155,6 @@ fun < // } // } // } - } LaunchedEffect(isResourceProviderReady, initState) { diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MapViewHolder.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewHolder.kt similarity index 72% rename from mapconductor-core/src/main/java/com/mapconductor/core/MapViewHolder.kt rename to mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewHolder.kt index 435148c8..13110a17 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MapViewHolder.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewHolder.kt @@ -1,6 +1,6 @@ -package com.mapconductor.core +package com.mapconductor.core.map interface MapViewHolder { val mapView: TMapView val map: TMap -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/MapViewHolderStore.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewHolderStore.kt similarity index 67% rename from mapconductor-core/src/main/java/com/mapconductor/core/MapViewHolderStore.kt rename to mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewHolderStore.kt index cb44adc9..f0fb1bf3 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/MapViewHolderStore.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewHolderStore.kt @@ -1,16 +1,18 @@ -package com.mapconductor.core +package com.mapconductor.core.map import android.content.Context abstract class StaticHolder { private val holders = mutableMapOf() - fun has(id: String): Boolean { - return holders.containsKey(id) - } + fun has(id: String): Boolean = holders.containsKey(id) fun get(id: String): ValueType? = holders[id] - fun set(id: String, viewHolder: ValueType) { + + fun set( + id: String, + viewHolder: ValueType, + ) { holders[id] = viewHolder } @@ -24,14 +26,14 @@ abstract class StaticHolder { } abstract class MapViewHolderStoreBaseAsync< - TMapView, - TMap, - TOptions - >: StaticHolder>() { + TMapView, + TMap, + TOptions, +> : + StaticHolder>() { abstract suspend fun getOrCreate( context: Context, id: String, options: TOptions, ): MapViewHolder - } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewScope.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewScope.kt index c97f5f0a..45768e68 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewScope.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewScope.kt @@ -1,6 +1,6 @@ package com.mapconductor.core.map -import com.mapconductor.core.MapOverlayRegistry +import com.mapconductor.core.map.MapOverlayRegistry import com.mapconductor.core.marker.MarkerEntry import com.mapconductor.core.marker.MarkerOverlay import kotlinx.coroutines.flow.MutableStateFlow @@ -9,9 +9,9 @@ open class MapViewScope { val markerFlow = MutableStateFlow>(emptyList()) val allMarkerKeys = HashSet() - fun buildRegistry() : MapOverlayRegistry { + fun buildRegistry(): MapOverlayRegistry { val registry = MapOverlayRegistry() registry.register(MarkerOverlay(markerFlow)) return registry } -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt index a3007c6a..0232988d 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/map/MapViewState.kt @@ -1,8 +1,6 @@ package com.mapconductor.core.map -import android.util.Log -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapDesignType +import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.features.IGeoPoint import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -10,6 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import android.util.Log enum class InitState { NotStarted, @@ -30,16 +29,27 @@ interface MapViewState { val mapDesignType: MapDesignType fun initAsync(init: suspend () -> Boolean) + fun resetInitState() - fun moveCameraTo(position: IMapCameraPosition, durationMs: Long = 0, listener: MoveCameraCallback? = null) - fun moveCameraTo(position: IGeoPoint, durationMs: Long = 0, listener: MoveCameraCallback? = null) + + fun moveCameraTo( + position: IMapCameraPosition, + durationMs: Long = 0, + listener: MoveCameraCallback? = null, + ) + + fun moveCameraTo( + position: IGeoPoint, + durationMs: Long = 0, + listener: MoveCameraCallback? = null, + ) // fun addMarkers(markerDataList: List, listener: AddMarkersCallback? = null) } abstract class MapViewStateImpl( protected val mainCoroutine: CoroutineScope = CoroutineScope(Dispatchers.Main), -): MapViewState { - private val TAG = this.javaClass.name +) : MapViewState { + private val tag = this.javaClass.name private val _isInitialized = MutableStateFlow(InitState.NotStarted) override val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -49,11 +59,11 @@ abstract class MapViewStateImpl( } protected fun warningLog(message: String) { - Log.w(TAG, message) + Log.w(tag, message) } protected fun debugLog(message: String) { - Log.d(TAG, message) + Log.d(tag, message) } override fun initAsync(init: suspend () -> Boolean) { @@ -71,3 +81,23 @@ abstract class MapViewStateImpl( } } } + +interface MapOverlay { + val flow: StateFlow> + + suspend fun render( + data: List, + controller: MapViewController, + ) +} + +class MapOverlayRegistry { + private val overlays = mutableListOf>() + + fun register(overlay: MapOverlay<*>) { + if (overlays.toSet().contains(overlay)) return + overlays.add(overlay) + } + + fun getAll(): List> = overlays +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/marker/Marker.kt b/mapconductor-core/src/main/java/com/mapconductor/core/marker/Marker.kt index 77579bbf..444aa926 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/marker/Marker.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/marker/Marker.kt @@ -1,12 +1,12 @@ package com.mapconductor.core.marker -import android.os.Parcelable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint import com.mapconductor.core.geocell.IdentifiedPoint import java.util.UUID +import android.os.Parcelable // ------- Core Types ---------- typealias MarkerClickHandler = (MarkerState) -> Unit @@ -21,7 +21,6 @@ class MarkerState( var extra: Parcelable? = null, icon: MarkerIconProp? = null, ) { - // -- position and positionState properties -- private val _position = mutableStateOf(position) val positionState: State get() = _position diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt index ac55c774..32efbf03 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerCompose.kt @@ -1,6 +1,5 @@ package com.mapconductor.core.marker -import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.remember @@ -9,8 +8,9 @@ import com.mapconductor.core.info.InfoBubbleSpec import com.mapconductor.core.info.LocalInfoBubbleCollector import com.mapconductor.core.map.MapViewScope import kotlinx.coroutines.flow.MutableStateFlow +import android.os.Parcelable -open class MarkerScope ( +open class MarkerScope( val bubbleCollector: MutableStateFlow>, ) @@ -50,13 +50,15 @@ fun MapViewScope.Marker( onClick: MarkerClickHandler? = null, content: (@Composable MarkerScope.() -> Unit)? = null, ) { - val handlers = MarkerHandlers( - onClick = onClick, - ) - val entry = MarkerEntry( - state = state, - handlers = handlers, - ) + val handlers = + MarkerHandlers( + onClick = onClick, + ) + val entry = + MarkerEntry( + state = state, + handlers = handlers, + ) Marker(entry, content) } @@ -68,17 +70,20 @@ fun MapViewScope.Marker( onClick: MarkerClickHandler? = null, content: (@Composable MarkerScope.() -> Unit)? = null, ) { - val state = MarkerState( - position = position, - extra = extra, - icon = icon, - ) - val handlers = MarkerHandlers( - onClick = onClick, - ) - val entry = MarkerEntry( - state = state, - handlers = handlers, - ) + val state = + MarkerState( + position = position, + extra = extra, + icon = icon, + ) + val handlers = + MarkerHandlers( + onClick = onClick, + ) + val entry = + MarkerEntry( + state = state, + handlers = handlers, + ) Marker(entry, content) -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerDsl.kt b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerDsl.kt index 516f194f..0b7f4dd0 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerDsl.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerDsl.kt @@ -1,8 +1,8 @@ package com.mapconductor.core.marker -import android.os.Parcelable import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.GeoPointBuilder +import android.os.Parcelable // ------- DSL Marker --------- @DslMarker @@ -29,25 +29,28 @@ class MarkerBuilder { } fun build(): MarkerEntry { - val initialPosition = GeoPoint( - latitude = point.latitude, - longitude = point.longitude, - altitude = point.altitude ?: 0.0, - ) - - val state = MarkerState( - position = initialPosition, - extra = extraData, - icon = iconData, - ) - - val handlers = MarkerHandlers( - onClick = onClick, - ) + val initialPosition = + GeoPoint( + latitude = point.latitude, + longitude = point.longitude, + altitude = point.altitude ?: 0.0, + ) + + val state = + MarkerState( + position = initialPosition, + extra = extraData, + icon = iconData, + ) + + val handlers = + MarkerHandlers( + onClick = onClick, + ) return MarkerEntry( state = state, handlers = handlers, ) } -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerIconProp.kt b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerIconProp.kt index 7cb92153..a0d1cb3d 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerIconProp.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerIconProp.kt @@ -1,9 +1,9 @@ package com.mapconductor.core.marker -import android.graphics.Color -import android.graphics.drawable.Drawable import androidx.compose.ui.geometry.Size import com.mapconductor.core.Offset +import android.graphics.Color +import android.graphics.drawable.Drawable data class MarkerIconProp( val fillColor: Int? = Color.RED, @@ -17,11 +17,11 @@ data class MarkerIconProp( val iconDrawable: Drawable? = null, val anchor: Offset = Offset(0.5, 1.0), val size: Size = Size(32f, 32f), - val infoAnchor: Offset = Offset(0.5, 0.5) + val infoAnchor: Offset = Offset(0.5, 0.5), ) // -//fun drawableToBitmap(drawable: Drawable, width: Int = 96, height: Int = 96): Bitmap { +// fun drawableToBitmap(drawable: Drawable, width: Int = 96, height: Int = 96): Bitmap { // val bmpWidth = drawable.intrinsicWidth.takeIf { it > 0 } ?: width // val bmpHeight = drawable.intrinsicHeight.takeIf { it > 0 } ?: height // @@ -30,11 +30,11 @@ data class MarkerIconProp( // drawable.setBounds(0, 0, canvas.width, canvas.height) // drawable.draw(canvas) // return bitmap -//} +// } // -//// -//@Composable -//fun RememberDrawable(@DrawableRes resId: Int): Bitmap { +// // +// @Composable +// fun RememberDrawable(@DrawableRes resId: Int): Bitmap { // val context = LocalContext.current // // val drawableResId = rememberSaveable { mutableStateOf(resId) } @@ -44,4 +44,4 @@ data class MarkerIconProp( // throw IllegalArgumentException("Resource is not available") // drawableToBitmap(drawable) // } -//} \ No newline at end of file +// } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerProps.kt b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerProps.kt index 058f4329..4800f8a5 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerProps.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/marker/MarkerProps.kt @@ -1,14 +1,14 @@ package com.mapconductor.core.marker -import android.graphics.Bitmap import androidx.compose.runtime.compositionLocalOf import androidx.compose.ui.geometry.Size -import com.mapconductor.core.MapOverlay import com.mapconductor.core.Offset import com.mapconductor.core.controller.MapViewController +import com.mapconductor.core.map.MapOverlay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import java.io.ByteArrayOutputStream +import android.graphics.Bitmap data class BitmapIcon( val bitmap: Bitmap, @@ -25,12 +25,15 @@ data class BitmapIcon( class MarkerOverlay( override val flow: StateFlow>, ) : MapOverlay { - - override suspend fun render(data: List, controller: MapViewController) { + override suspend fun render( + data: List, + controller: MapViewController, + ) { controller.addMarkers(data) } } -val LocalMarkerCollector = compositionLocalOf>> { - error("Marker must be under the ") -} +val LocalMarkerCollector = + compositionLocalOf>> { + error("Marker must be under the ") + } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/projection/Projection.kt b/mapconductor-core/src/main/java/com/mapconductor/core/projection/Projection.kt index e886c7c7..8029b46f 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/projection/Projection.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/projection/Projection.kt @@ -4,6 +4,7 @@ import com.mapconductor.core.Offset import com.mapconductor.core.features.IGeoPoint interface Projection { - fun project(position: IGeoPoint) : Offset + fun project(position: IGeoPoint): Offset + fun unproject(point: Offset): IGeoPoint -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/projection/WGS84.kt b/mapconductor-core/src/main/java/com/mapconductor/core/projection/WGS84.kt index 68ea0b58..24633aee 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/projection/WGS84.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/projection/WGS84.kt @@ -17,13 +17,11 @@ import com.mapconductor.core.features.IGeoPoint * 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. - */ -/* + * * port from here * https://github.com/googlemaps/android-maps-utils/blob/70a77b066b8391da06a2d708792de8337bf5d3b6/library/src/main/java/com/google/maps/android/projection/SphericalMercatorProjection.java */ -object WGS84: Projection { - +object WGS84 : Projection { override fun project(position: IGeoPoint): Offset { val x = position.longitude / 360 + .5 val siny = Math.sin(Math.toRadians(position.latitude)) @@ -42,4 +40,4 @@ object WGS84: Projection { override val altitude: Double? = null } } -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/projection/WebMercator.kt b/mapconductor-core/src/main/java/com/mapconductor/core/projection/WebMercator.kt index 11a3a0fd..488e5b4c 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/projection/WebMercator.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/projection/WebMercator.kt @@ -7,7 +7,7 @@ import kotlin.math.exp import kotlin.math.ln import kotlin.math.tan -object WebMercator: Projection { +object WebMercator : Projection { override fun project(position: IGeoPoint): Offset { val x = position.longitude * 20037508.34 / 180 val y = ln(tan((90 + position.latitude) * Math.PI / 360)) * 20037508.34 / Math.PI @@ -23,5 +23,4 @@ object WebMercator: Projection { override val altitude: Double? = null } } - -} \ No newline at end of file +} diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/spherical/haversinDistance.kt b/mapconductor-core/src/main/java/com/mapconductor/core/spherical/HaversinDistance.kt similarity index 72% rename from mapconductor-core/src/main/java/com/mapconductor/core/spherical/haversinDistance.kt rename to mapconductor-core/src/main/java/com/mapconductor/core/spherical/HaversinDistance.kt index f15ece96..9e5b74af 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/spherical/haversinDistance.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/spherical/HaversinDistance.kt @@ -7,17 +7,21 @@ import kotlin.math.pow import kotlin.math.sin import kotlin.math.sqrt -fun haversineDistance(p1: IGeoPoint, p2: IGeoPoint): Double { - val R = 6371000.0 // 地球の半径(m) +fun haversineDistance( + p1: IGeoPoint, + p2: IGeoPoint, +): Double { + val earthR = 6371000.0 // 地球の半径(m) val lat1 = Math.toRadians(p1.latitude) val lat2 = Math.toRadians(p2.latitude) val dLat = lat2 - lat1 val dLon = Math.toRadians(p2.longitude - p1.longitude) - val a = sin(dLat / 2).pow(2.0) + + val a = + sin(dLat / 2).pow(2.0) + cos(lat1) * cos(lat2) * sin(dLon / 2).pow(2.0) val c = 2 * atan2(sqrt(a), sqrt(1 - a)) - return R * c + return earthR * c } diff --git a/mapconductor-core/src/main/java/com/mapconductor/core/state/StaticOrValue.kt b/mapconductor-core/src/main/java/com/mapconductor/core/state/StaticOrValue.kt index d3e32408..fa8a3597 100644 --- a/mapconductor-core/src/main/java/com/mapconductor/core/state/StaticOrValue.kt +++ b/mapconductor-core/src/main/java/com/mapconductor/core/state/StaticOrValue.kt @@ -1,12 +1,12 @@ package com.mapconductor.core.state -import android.annotation.SuppressLint import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import android.annotation.SuppressLint sealed class StateOrValue { abstract val value: T @@ -14,24 +14,31 @@ sealed class StateOrValue { open fun update(newValue: T) { // Immutable by default } + fun isDynamic() = this is StateOrValue.Dynamic + fun isStatic() = this is StateOrValue.Static @SuppressLint("UnrememberedMutableState") @Composable - fun derived(transform: (T) -> R): StateOrValue = when (this) { - is Static -> Static(transform(value)) - is Dynamic -> { - val derivedState = derivedStateOf { transform(this.state.value) } - Dynamic(derivedState) + fun derived(transform: (T) -> R): StateOrValue = + when (this) { + is Static -> Static(transform(value)) + is Dynamic -> { + val derivedState = derivedStateOf { transform(this.state.value) } + Dynamic(derivedState) + } } - } - class Static(private val raw: T) : StateOrValue() { + class Static( + private val raw: T, + ) : StateOrValue() { override val value: T get() = raw } - class Dynamic(val state: State) : StateOrValue() { + class Dynamic( + val state: State, + ) : StateOrValue() { override val value: T get() = state.value override fun update(newValue: T) { @@ -43,9 +50,12 @@ sealed class StateOrValue { } fun T.toStateOrValue(): StateOrValue = StateOrValue.Static(this) + fun State.toStateOrValue(): StateOrValue = StateOrValue.Dynamic(this) + @Composable -fun StateOrValue.asState(): State = when (this) { - is StateOrValue.Static -> remember { mutableStateOf(value) } - is StateOrValue.Dynamic -> this.state -} \ No newline at end of file +fun StateOrValue.asState(): State = + when (this) { + is StateOrValue.Static -> remember { mutableStateOf(value) } + is StateOrValue.Dynamic -> this.state + } diff --git a/mapconductor-core/src/test/java/com/mapconductor/core/ExampleUnitTest.kt b/mapconductor-core/src/test/java/com/mapconductor/core/ExampleUnitTest.kt index b664258d..f14e7415 100644 --- a/mapconductor-core/src/test/java/com/mapconductor/core/ExampleUnitTest.kt +++ b/mapconductor-core/src/test/java/com/mapconductor/core/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.mapconductor.core +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/build.gradle.kts b/mapconductor-for-arcgis/build.gradle.kts index f10f4b65..c0acf5c8 100644 --- a/mapconductor-for-arcgis/build.gradle.kts +++ b/mapconductor-for-arcgis/build.gradle.kts @@ -3,6 +3,15 @@ plugins { id("org.jetbrains.kotlin.android") id("maven-publish") alias(libs.plugins.kotlin.compose) + id("org.jlleitschuh.gradle.ktlint") +} + +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } } android { @@ -29,7 +38,7 @@ android { isMinifyEnabled = project.property("isMinifyEnabled").toString().toBoolean() proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } @@ -63,4 +72,4 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt b/mapconductor-for-arcgis/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt index 9f2e4d49..e1e339d8 100644 --- a/mapconductor-for-arcgis/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt +++ b/mapconductor-for-arcgis/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.mapconductor.googlemaps -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.mapconductor.googlemaps", appContext.packageName) } -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt index 9ddb335e..8909c017 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapDesignType.kt @@ -1,25 +1,22 @@ package com.mapconductor.arcgis import com.arcgismaps.mapping.BasemapStyle -import com.mapconductor.core.MapDesignType +import com.mapconductor.core.map.MapDesignType interface ArcGISDesignType : MapDesignType data class ArcGISDesign( override val id: String, val elevationSources: List = emptyList(), -): ArcGISDesignType { - +) : ArcGISDesignType { override fun getValue(): String = id - fun withElevationSources(sources: List): ArcGISDesign { - return copy( + fun withElevationSources(sources: List): ArcGISDesign = + copy( elevationSources = sources, ) - } companion object { - val Streets = ArcGISDesign("arc_gis_streets") val Imagery = ArcGISDesign("arc_gis_imagery") val ImageryStandard = ArcGISDesign("arc_gis_imagery_standard") @@ -80,73 +77,77 @@ data class ArcGISDesign( val OsmHybridDetail = ArcGISDesign("osm_hybrid_detail") val OsmNavigation = ArcGISDesign("osm_navigation") val OsmNavigationDark = ArcGISDesign("osm_navigation_dark") - - fun Create(id: String, sources: List = emptyList()): ArcGISDesign = when(id) { - Streets.id -> Streets - Imagery.id -> Imagery - ImageryStandard.id -> ImageryStandard - ImageryLabels.id -> ImageryLabels - LightGray.id -> LightGray - LightGrayBase.id -> LightGrayBase - LightGrayLabels.id -> LightGrayLabels - DarkGray.id -> DarkGray - DarkGrayBase.id -> DarkGrayBase - DarkGrayLabels.id -> DarkGrayLabels - Navigation.id -> Navigation - NavigationNight.id -> NavigationNight - StreetsNight.id -> StreetsNight - StreetsRelief.id -> StreetsRelief - Topographic.id -> Topographic - Oceans.id -> Oceans - OceansBase.id -> OceansBase - OceansLabels.id -> OceansLabels - Terrain.id -> Terrain - TerrainBase.id -> TerrainBase - TerrainDetail.id -> TerrainDetail - Community.id -> Community - ChartedTerritory.id -> ChartedTerritory - ColoredPencil.id -> ColoredPencil - Nova.id -> Nova - ModernAntique.id -> ModernAntique - Midcentury.id -> Midcentury - Newspaper.id -> Newspaper - HillshadeLight.id -> HillshadeLight - HillshadeDark.id -> HillshadeDark - StreetsReliefBase.id -> StreetsReliefBase - TopographicBase.id -> TopographicBase - ChartedTerritoryBase.id -> ChartedTerritoryBase - ModernAntiqueBase.id -> ModernAntiqueBase - HumanGeography.id -> HumanGeography - HumanGeographyBase.id -> HumanGeographyBase - HumanGeographyDetail.id -> HumanGeographyDetail - HumanGeographyLabels.id -> HumanGeographyLabels - HumanGeographyDark.id -> HumanGeographyDark - HumanGeographyDarkBase.id -> HumanGeographyDarkBase - HumanGeographyDarkDetail.id -> HumanGeographyDarkDetail - HumanGeographyDarkLabels.id -> HumanGeographyDarkLabels - Outdoor.id -> Outdoor - OsmStandard.id -> OsmStandard - OsmStandardRelief.id -> OsmStandardRelief - OsmStandardReliefBase.id -> OsmStandardReliefBase - OsmStreets.id -> OsmStreets - OsmStreetsRelief.id -> OsmStreetsRelief - OsmLightGray.id -> OsmLightGray - OsmLightGrayBase.id -> OsmLightGrayBase - OsmLightGrayLabels.id -> OsmLightGrayLabels - OsmDarkGray.id -> OsmDarkGray - OsmDarkGrayBase.id -> OsmDarkGrayBase - OsmDarkGrayLabels.id -> OsmDarkGrayLabels - OsmStreetsReliefBase.id -> OsmStreetsReliefBase - OsmBlueprint.id -> OsmBlueprint - OsmHybrid.id -> OsmHybrid - OsmHybridDetail.id -> OsmHybridDetail - OsmNavigation.id -> OsmNavigation - OsmNavigationDark.id -> OsmNavigationDark - else -> throw Throwable("unknown design id: \"$id\"") - } - fun toBasemapStyle(designType: ArcGISDesignType): BasemapStyle { - return when(designType.getValue()) { + fun Create( + id: String, + sources: List = emptyList(), + ): ArcGISDesign = + when (id) { + Streets.id -> Streets + Imagery.id -> Imagery + ImageryStandard.id -> ImageryStandard + ImageryLabels.id -> ImageryLabels + LightGray.id -> LightGray + LightGrayBase.id -> LightGrayBase + LightGrayLabels.id -> LightGrayLabels + DarkGray.id -> DarkGray + DarkGrayBase.id -> DarkGrayBase + DarkGrayLabels.id -> DarkGrayLabels + Navigation.id -> Navigation + NavigationNight.id -> NavigationNight + StreetsNight.id -> StreetsNight + StreetsRelief.id -> StreetsRelief + Topographic.id -> Topographic + Oceans.id -> Oceans + OceansBase.id -> OceansBase + OceansLabels.id -> OceansLabels + Terrain.id -> Terrain + TerrainBase.id -> TerrainBase + TerrainDetail.id -> TerrainDetail + Community.id -> Community + ChartedTerritory.id -> ChartedTerritory + ColoredPencil.id -> ColoredPencil + Nova.id -> Nova + ModernAntique.id -> ModernAntique + Midcentury.id -> Midcentury + Newspaper.id -> Newspaper + HillshadeLight.id -> HillshadeLight + HillshadeDark.id -> HillshadeDark + StreetsReliefBase.id -> StreetsReliefBase + TopographicBase.id -> TopographicBase + ChartedTerritoryBase.id -> ChartedTerritoryBase + ModernAntiqueBase.id -> ModernAntiqueBase + HumanGeography.id -> HumanGeography + HumanGeographyBase.id -> HumanGeographyBase + HumanGeographyDetail.id -> HumanGeographyDetail + HumanGeographyLabels.id -> HumanGeographyLabels + HumanGeographyDark.id -> HumanGeographyDark + HumanGeographyDarkBase.id -> HumanGeographyDarkBase + HumanGeographyDarkDetail.id -> HumanGeographyDarkDetail + HumanGeographyDarkLabels.id -> HumanGeographyDarkLabels + Outdoor.id -> Outdoor + OsmStandard.id -> OsmStandard + OsmStandardRelief.id -> OsmStandardRelief + OsmStandardReliefBase.id -> OsmStandardReliefBase + OsmStreets.id -> OsmStreets + OsmStreetsRelief.id -> OsmStreetsRelief + OsmLightGray.id -> OsmLightGray + OsmLightGrayBase.id -> OsmLightGrayBase + OsmLightGrayLabels.id -> OsmLightGrayLabels + OsmDarkGray.id -> OsmDarkGray + OsmDarkGrayBase.id -> OsmDarkGrayBase + OsmDarkGrayLabels.id -> OsmDarkGrayLabels + OsmStreetsReliefBase.id -> OsmStreetsReliefBase + OsmBlueprint.id -> OsmBlueprint + OsmHybrid.id -> OsmHybrid + OsmHybridDetail.id -> OsmHybridDetail + OsmNavigation.id -> OsmNavigation + OsmNavigationDark.id -> OsmNavigationDark + else -> throw Throwable("unknown design id: \"$id\"") + } + + fun toBasemapStyle(designType: ArcGISDesignType): BasemapStyle = + when (designType.getValue()) { Streets.id -> BasemapStyle.ArcGISStreets Imagery.id -> BasemapStyle.ArcGISImagery ImageryStandard.id -> BasemapStyle.ArcGISImageryStandard @@ -209,7 +210,5 @@ data class ArcGISDesign( OsmNavigationDark.id -> BasemapStyle.OsmNavigationDark else -> throw Throwable("unknown design id: \"$designType.id\"") } - } } } - diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt index cec45355..3c4d26af 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapView.kt @@ -1,6 +1,5 @@ package com.mapconductor.arcgis -import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember @@ -12,6 +11,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner import com.mapconductor.core.map.MapViewBase import kotlinx.coroutines.launch +import android.view.ViewGroup @Composable fun ArcGISMapView( @@ -37,26 +37,28 @@ fun ArcGISMapView( scope = scope, registry = registry, onInitialize = { - val basemapStyle = ArcGISDesign.toBasemapStyle(state.mapDesignType) - val options = ArcGISMapViewInitOptions( - basemapStyle = basemapStyle, - elevationSources = state.mapDesignType.elevationSources, - ) + val options = + ArcGISMapViewInitOptions( + basemapStyle = basemapStyle, + elevationSources = state.mapDesignType.elevationSources, + ) - val holder = ArcGISMapViewHolderStore.getOrCreate( - context = context, - id = state.stateId, - options = options, - ) + val holder = + ArcGISMapViewHolderStore.getOrCreate( + context = context, + id = state.stateId, + options = options, + ) holder.mapView.onCreate(owner) holder.mapView.onResume(owner) val eventHandler = state - val controller = ArcGISMapViewController( - holder = holder, - eventHandler = eventHandler, - ) + val controller = + ArcGISMapViewController( + holder = holder, + eventHandler = eventHandler, + ) state.controller = controller controller.coroutine.launch { holder.map.viewpointChanged @@ -65,41 +67,45 @@ fun ArcGISMapView( eventHandler.onCameraMove(camera) } } - val restoreCameraPosition = state.mapCameraPosition.value ?: - MapCameraPosition.from(state.initCameraPosition) + val restoreCameraPosition = + state.mapCameraPosition.value + ?: MapCameraPosition.from(state.initCameraPosition) controller.moveCamera(restoreCameraPosition) controllerRef.value = controller holderRef.value = holder true }, - customDisposableEffect = {_state, _holderRef -> + customDisposableEffect = { _state, _holderRef -> // ArcGIS specific DisposableEffect logic DisposableEffect(lifecycle) { val stateId = _state.stateId // from BaseMapViewState - val observer = object : DefaultLifecycleObserver { - override fun onResume(owner: LifecycleOwner) { - _holderRef.value?.mapView?.onResume(owner) - } - override fun onPause(owner: LifecycleOwner) { - _holderRef.value?.mapView?.onPause(owner) - } - override fun onDestroy(owner: LifecycleOwner) { - val currentHolder = _holderRef.value - if (currentHolder != null) { - val activity = context.findActivity() - if (activity?.isChangingConfigurations == true) { - (currentHolder.mapView.parent as? ViewGroup)?.removeView(currentHolder.mapView) - } else { - // Ensure these calls are safe if mapView might be null or already destroyed - currentHolder.mapView.onPause(owner) - currentHolder.mapView.onDestroy(owner) - ArcGISMapViewHolderStore.remove(stateId) // Clean up from your store + val observer = + object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + _holderRef.value?.mapView?.onResume(owner) + } + + override fun onPause(owner: LifecycleOwner) { + _holderRef.value?.mapView?.onPause(owner) + } + + override fun onDestroy(owner: LifecycleOwner) { + val currentHolder = _holderRef.value + if (currentHolder != null) { + val activity = context.findActivity() + if (activity?.isChangingConfigurations == true) { + (currentHolder.mapView.parent as? ViewGroup)?.removeView(currentHolder.mapView) + } else { + // Ensure these calls are safe if mapView might be null or already destroyed + currentHolder.mapView.onPause(owner) + currentHolder.mapView.onDestroy(owner) + ArcGISMapViewHolderStore.remove(stateId) // Clean up from your store + } } } } - } lifecycle.addObserver(observer) onDispose { _state.resetInitState() @@ -109,4 +115,4 @@ fun ArcGISMapView( }, content = content, ) -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt index cd9edc24..02b237e6 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewController.kt @@ -17,11 +17,18 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.lang.ref.WeakReference -import java.util.WeakHashMap -interface IArcGISMapViewController: MapViewController { - fun moveCamera(dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null) - fun animateCamera(dstPosition: MapCameraPosition, duration: Int, listener: MapViewState.MoveCameraCallback? = null) +interface IArcGISMapViewController : MapViewController { + fun moveCamera( + dstPosition: MapCameraPosition, + listener: MapViewState.MoveCameraCallback? = null, + ) + + fun animateCamera( + dstPosition: MapCameraPosition, + duration: Int, + listener: MapViewState.MoveCameraCallback? = null, + ) } class ArcGISMapViewController( @@ -30,88 +37,97 @@ class ArcGISMapViewController( override val coroutine: CoroutineScope = CoroutineScope(Dispatchers.Default), ) : IArcGISMapViewController { lateinit var markerLayer: GraphicsOverlay - private val _markers: WeakHashMap = WeakHashMap() - private val _markerProps: HashMap = HashMap() private val eventHandlerRef = WeakReference(eventHandler) - private val baseMapViewController = BaseMapViewController( - coroutine = coroutine, - onMarkerRemove = { id, marker -> - this.markerLayer.graphics.remove(marker) + private val baseMapViewController = + BaseMapViewController( + coroutine = coroutine, + onMarkerRemove = { id, marker -> + this.markerLayer.graphics.remove(marker) - coroutine.launch { - eventHandlerRef.get()?.onMarkerRemove(id) - } - }, - onMarkerAdd = { newMarkers -> - val markers = newMarkers.map { params -> - val bitmapDrawable = params.icon.bitmap.toDrawable(holder.mapView.context.resources) - val density = ResourceProvider.density - val width = (params.icon.size.width / density) - val height = (params.icon.size.height / density) - val anchorX = (params.icon.anchor.x - 0.5) * width - val anchorY = (params.icon.anchor.y - 0.5) * height - - val pictureSymbolFuture = PictureMarkerSymbol.createWithImage(bitmapDrawable).also { - it.width = width.toFloat() - it.height = height.toFloat() - it.offsetX = anchorX.toFloat() - it.offsetY = anchorY.toFloat() + coroutine.launch { + eventHandlerRef.get()?.onMarkerRemove(id) } - - val marker = Graphic( - geometry = params.entry.state.position.toPoint(), - symbol = pictureSymbolFuture, - ) - marker.attributes.set("id", params.entry.id) - return@map marker - } - - this.markerLayer.graphics.addAll(markers) - - return@BaseMapViewController markers - }, - onMarkerChanged = { changes -> - changes.forEach { params -> - // TODO: アイコンに変更があったかどうかを比較 - val bitmapDrawable = params.icon.bitmap.toDrawable(holder.mapView.context.resources) - val density = ResourceProvider.density - val width = (params.icon.size.width / density) - val height = (params.icon.size.height / density) - val anchorX = (params.icon.anchor.x - 0.5) * width - val anchorY = (params.icon.anchor.y - 0.5) * height - - val pictureSymbolFuture = PictureMarkerSymbol.createWithImage(bitmapDrawable).also { - it.width = width.toFloat() - it.height = height.toFloat() - it.offsetX = anchorX.toFloat() - it.offsetY = anchorY.toFloat() + }, + onMarkerAdd = { newMarkers -> + val markers = + newMarkers.map { params -> + val bitmapDrawable = params.icon.bitmap.toDrawable(holder.mapView.context.resources) + val density = ResourceProvider.density + val width = (params.icon.size.width / density) + val height = (params.icon.size.height / density) + val anchorX = (params.icon.anchor.x - 0.5) * width + val anchorY = (params.icon.anchor.y - 0.5) * height + + val pictureSymbolFuture = + PictureMarkerSymbol.createWithImage(bitmapDrawable).also { + it.width = width.toFloat() + it.height = height.toFloat() + it.offsetX = anchorX.toFloat() + it.offsetY = anchorY.toFloat() + } + + val marker = + Graphic( + geometry = + params.entry.state.position + .toPoint(), + symbol = pictureSymbolFuture, + ) + marker.attributes.set("id", params.entry.id) + return@map marker + } + + this.markerLayer.graphics.addAll(markers) + + return@BaseMapViewController markers + }, + onMarkerChanged = { changes -> + changes.forEach { params -> + // TODO: アイコンに変更があったかどうかを比較 + val bitmapDrawable = params.icon.bitmap.toDrawable(holder.mapView.context.resources) + val density = ResourceProvider.density + val width = (params.icon.size.width / density) + val height = (params.icon.size.height / density) + val anchorX = (params.icon.anchor.x - 0.5) * width + val anchorY = (params.icon.anchor.y - 0.5) * height + + val pictureSymbolFuture = + PictureMarkerSymbol.createWithImage(bitmapDrawable).also { + it.width = width.toFloat() + it.height = height.toFloat() + it.offsetX = anchorX.toFloat() + it.offsetY = anchorY.toFloat() + } + + params.marker.geometry = + params.entry.state.position + .toPoint() + params.marker.symbol = pictureSymbolFuture } - - params.marker.geometry = params.entry.state.position.toPoint() - params.marker.symbol = pictureSymbolFuture - } - }, - ) + }, + ) init { - this.markerLayer = GraphicsOverlay().apply { - sceneProperties.surfacePlacement = SurfacePlacement.Relative - } + this.markerLayer = + GraphicsOverlay().apply { + sceneProperties.surfacePlacement = SurfacePlacement.Relative + } holder.map.graphicsOverlays.clear() holder.map.graphicsOverlays.add(markerLayer) } - override suspend fun addMarkers(markerList: List) = - baseMapViewController.addMarkers(markerList) + + override suspend fun addMarkers(markerList: List) = baseMapViewController.addMarkers(markerList) override suspend fun clearOverlays() = baseMapViewController.clearOverlays() override fun toScreenOffset(position: IGeoPoint): Offset? { - val result = this.holder.map.locationToScreen( - point = GeoPoint.from(position).toPoint(), - ) + val result = + this.holder.map.locationToScreen( + point = GeoPoint.from(position).toPoint(), + ) return result?.let { Offset(it.screenPoint.x, it.screenPoint.y) } @@ -119,7 +135,7 @@ class ArcGISMapViewController( override fun moveCamera( dstPosition: MapCameraPosition, - listener: MapViewState.MoveCameraCallback? + listener: MapViewState.MoveCameraCallback?, ) { val dstCameraPosition = dstPosition.toCamera() @@ -132,17 +148,17 @@ class ArcGISMapViewController( override fun animateCamera( dstPosition: MapCameraPosition, duration: Int, - listener: MapViewState.MoveCameraCallback? + listener: MapViewState.MoveCameraCallback?, ) { - val dstCameraPosition = dstPosition.toCamera() coroutine.launch { - val result = holder.map.setViewpointCameraAnimated( - camera = dstCameraPosition, - duration = duration.toFloat() / 1000.0f, - ) + val result = + holder.map.setViewpointCameraAnimated( + camera = dstCameraPosition, + duration = duration.toFloat() / 1000.0f, + ) listener?.onComplete(result.isSuccess) } } -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderImpl.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderImpl.kt index f1eff470..5ca8623d 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderImpl.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderImpl.kt @@ -1,18 +1,18 @@ package com.mapconductor.arcgis -import android.content.Context -import android.content.pm.PackageManager -import android.util.AttributeSet -import android.widget.FrameLayout import androidx.lifecycle.LifecycleOwner import com.arcgismaps.ApiKey import com.arcgismaps.ArcGISEnvironment import com.arcgismaps.mapping.ArcGISScene import com.arcgismaps.mapping.ArcGISTiledElevationSource import com.arcgismaps.mapping.view.SceneView -import com.mapconductor.core.MapViewHolder +import com.mapconductor.core.map.MapViewHolder +import android.content.Context +import android.content.pm.PackageManager +import android.util.AttributeSet +import android.widget.FrameLayout -class WrapSceneView: FrameLayout { +class WrapSceneView : FrameLayout { lateinit var sceneView: SceneView constructor(context: Context) : super(context) @@ -22,22 +22,27 @@ class WrapSceneView: FrameLayout { fun onCreate(owner: LifecycleOwner) { this.sceneView.onCreate(owner) } + fun onPause(owner: LifecycleOwner) { this.sceneView.onPause(owner) } + fun onResume(owner: LifecycleOwner) { this.sceneView.onResume(owner) } + fun onStop(owner: LifecycleOwner) { this.sceneView.onStop(owner) } + fun onDestroy(owner: LifecycleOwner) { this.sceneView.onDestroy(owner) } } + class ArcGISMapViewHolderImpl private constructor( override val mapView: WrapSceneView, -): MapViewHolder { +) : MapViewHolder { override lateinit var map: SceneView companion object { @@ -50,12 +55,12 @@ class ArcGISMapViewHolderImpl private constructor( ArcGISEnvironment.apiKey = ApiKey.create(apiKey) val sceneView = SceneView(context) - val wrapView = WrapSceneView(context).apply { - addView(sceneView, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) - } + val wrapView = + WrapSceneView(context).apply { + addView(sceneView, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) + } wrapView.sceneView = sceneView - val holder = ArcGISMapViewHolderImpl(wrapView) val scene = ArcGISScene(options.basemapStyle) options.elevationSources.forEach { @@ -72,5 +77,7 @@ class ArcGISMapViewHolderImpl private constructor( } internal fun Context.getArcGisApiKey(): String? = - packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) - .metaData?.getString("ARCGIS_API_KEY") + packageManager + .getApplicationInfo(packageName, PackageManager.GET_META_DATA) + .metaData + ?.getString("ARCGIS_API_KEY") diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderStore.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderStore.kt index b44d9fd3..efe615a2 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderStore.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewHolderStore.kt @@ -1,9 +1,9 @@ package com.mapconductor.arcgis -import android.content.Context import com.arcgismaps.mapping.view.SceneView -import com.mapconductor.core.MapViewHolder -import com.mapconductor.core.MapViewHolderStoreBaseAsync +import com.mapconductor.core.map.MapViewHolder +import com.mapconductor.core.map.MapViewHolderStoreBaseAsync +import android.content.Context typealias ArcGISMapViewHolder = MapViewHolder @@ -12,15 +12,16 @@ object ArcGISMapViewHolderStore : override suspend fun getOrCreate( context: Context, id: String, - options: ArcGISMapViewInitOptions + options: ArcGISMapViewInitOptions, ): MapViewHolder { val existing = this.get(id) if (existing != null) return existing - val newHolder = ArcGISMapViewHolderImpl.create( - context = context.applicationContext, - options = options, - ) + val newHolder = + ArcGISMapViewHolderImpl.create( + context = context.applicationContext, + options = options, + ) this.set(id, newHolder) return newHolder } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewInitOptions.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewInitOptions.kt index d9ebc61f..c96b7710 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewInitOptions.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewInitOptions.kt @@ -5,4 +5,4 @@ import com.arcgismaps.mapping.BasemapStyle data class ArcGISMapViewInitOptions( val basemapStyle: BasemapStyle, val elevationSources: List, -) \ No newline at end of file +) diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewScope.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewScope.kt index 8228974d..456473de 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewScope.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGISMapViewScope.kt @@ -4,4 +4,4 @@ import com.mapconductor.core.map.MapViewScope class ArcGISMapViewScope : MapViewScope() { // 他の地図SDKにはない機能は、ここで定義する -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt index 96789d6f..ae6acb89 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/ArcGisMapViewState.kt @@ -1,22 +1,18 @@ package com.mapconductor.arcgis -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import com.arcgismaps.mapping.view.Camera -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapCameraPositionBase -import com.mapconductor.core.MapPaddings -import com.mapconductor.core.MapPaddingsImpl import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.map.IMapCameraPosition import com.mapconductor.core.map.InitState +import com.mapconductor.core.map.MapCameraPositionBase +import com.mapconductor.core.map.MapPaddings +import com.mapconductor.core.map.MapPaddingsImpl import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.MapViewStateImpl import com.mapconductor.core.marker.MarkerState @@ -27,17 +23,20 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import java.util.UUID +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.os.Bundle -interface IArcGISMapViewState: MapViewState { - -} +interface IArcGISMapViewState : MapViewState class ArcGISMapViewState( override val stateId: String, override val initCameraPosition: IMapCameraPosition, - override val mapDesignType: ArcGISDesign - -): MapViewStateImpl(), IArcGISMapViewState, IArcGISMapEventHandler { + override val mapDesignType: ArcGISDesign, +) : MapViewStateImpl(), + IArcGISMapViewState, + IArcGISMapEventHandler { // Map padding private val _padding = MutableStateFlow(MapPaddingsImpl.Zeros) val padding: StateFlow = _padding.asStateFlow() @@ -45,8 +44,7 @@ class ArcGISMapViewState( internal var controller: IArcGISMapViewController? = null // Camera position - private val _cameraPosition = MutableStateFlow(null) - private val cameraPosition: StateFlow = _cameraPosition.asStateFlow() + private val cameraPosition = MutableStateFlow(null) override val mapCameraPosition: StateFlow = cameraPosition.map { it?.toMapCameraPosition() }.stateIn( scope = mainCoroutine, @@ -57,7 +55,7 @@ class ArcGISMapViewState( override fun moveCameraTo( position: IMapCameraPosition, durationMs: Long, - listener: MapViewState.MoveCameraCallback? + listener: MapViewState.MoveCameraCallback?, ) { if (this.isInitialized.value != InitState.Initialized) { this.warningLog("moveCameraTo() called before map is initialized.") @@ -78,13 +76,13 @@ class ArcGISMapViewState( override fun moveCameraTo( position: IGeoPoint, durationMs: Long, - listener: MapViewState.MoveCameraCallback? + listener: MapViewState.MoveCameraCallback?, ) { // Do nothing here } override fun onCameraMove(cameraPosition: Camera) { - this._cameraPosition.value = cameraPosition + this.cameraPosition.value = cameraPosition } override fun onMarkerRemove(id: String) { @@ -96,54 +94,65 @@ class ArcGISMapViewState( } } -val ArcGISMapViewStateSaver = Saver( - save = { state -> - val cameraStateBundle = state.mapCameraPosition.value.let { cameraState -> +val ArcGISMapViewStateSaver = + Saver( + save = { state -> + val cameraStateBundle = + state.mapCameraPosition.value.let { cameraState -> + Bundle().apply { + putDouble("zoom", cameraState?.zoom ?: MapCameraPositionBase.Default.zoom) + putDouble("tilt", cameraState?.tilt ?: MapCameraPositionBase.Default.tilt) + putDouble("bearing", cameraState?.bearing ?: MapCameraPositionBase.Default.bearing) + putDouble( + "latitude", + cameraState?.position?.latitude + ?: MapCameraPositionBase.Default.position.latitude, + ) + putDouble( + "longitude", + cameraState?.position?.longitude + ?: MapCameraPositionBase.Default.position.longitude, + ) + } + } + + val mapDesignBundle = + Bundle().apply { + putString("id", state.mapDesignType.id) + } + Bundle().apply { - putDouble("zoom", cameraState?.zoom ?: MapCameraPositionBase.Default.zoom) - putDouble("tilt", cameraState?.tilt ?: MapCameraPositionBase.Default.tilt) - putDouble("bearing", cameraState?.bearing ?: MapCameraPositionBase.Default.bearing) - putDouble("latitude", - cameraState?.position?.latitude ?: - MapCameraPositionBase.Default.position.latitude) - putDouble("longitude", - cameraState?.position?.longitude ?: - MapCameraPositionBase.Default.position.longitude) + putString("stateId", state.stateId) + putBundle("mapDesign", mapDesignBundle) + putBundle("camera", cameraStateBundle) } - } - - val mapDesignBundle = Bundle().apply { - putString("id", state.mapDesignType.id) - } - - Bundle().apply { - putString("stateId", state.stateId) - putBundle("mapDesign", mapDesignBundle) - putBundle("camera", cameraStateBundle) - } - }, - restore = { storedData -> - val cameraBundle = storedData.getBundle("camera") - val mapDesignBundle = storedData.getBundle("mapDesign") - - ArcGISMapViewState( - stateId = storedData.getString("stateId")!!, - mapDesignType = ArcGISDesign.Create( - id = mapDesignBundle?.getString("id") ?: ArcGISDesign.Streets.id, - ), - initCameraPosition = MapCameraPosition( - position = GeoPoint.fromLatLong( - latitude = cameraBundle?.getDouble("latitude") ?: 0.0, - longitude = cameraBundle?.getDouble("longitude") ?: 0.0, - ), - zoom = cameraBundle?.getDouble("zoom") ?: 0.0, - bearing = cameraBundle?.getDouble("bearing") ?: 0.0, - tilt = cameraBundle?.getDouble("tilt") ?: 0.0, - paddings = null + }, + restore = { storedData -> + val cameraBundle = storedData.getBundle("camera") + val mapDesignBundle = storedData.getBundle("mapDesign") + + ArcGISMapViewState( + stateId = storedData.getString("stateId")!!, + mapDesignType = + ArcGISDesign.Create( + id = mapDesignBundle?.getString("id") ?: ArcGISDesign.Streets.id, + ), + initCameraPosition = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = cameraBundle?.getDouble("latitude") ?: 0.0, + longitude = cameraBundle?.getDouble("longitude") ?: 0.0, + ), + zoom = cameraBundle?.getDouble("zoom") ?: 0.0, + bearing = cameraBundle?.getDouble("bearing") ?: 0.0, + tilt = cameraBundle?.getDouble("tilt") ?: 0.0, + paddings = null, + ), ) - ) - }, -) + }, + ) + @Composable fun rememberArcGISMapViewState( mapDesign: ArcGISDesign = ArcGISDesign.Streets, @@ -153,15 +162,18 @@ fun rememberArcGISMapViewState( val uuid = UUID.randomUUID().toString() mutableStateOf(uuid) } - val state = rememberSaveable( - stateSaver = ArcGISMapViewStateSaver, - ) { - mutableStateOf(ArcGISMapViewState( - stateId = stateId, - mapDesignType = mapDesign, - initCameraPosition = MapCameraPosition.from(cameraPosition), - )) - } + val state = + rememberSaveable( + stateSaver = ArcGISMapViewStateSaver, + ) { + mutableStateOf( + ArcGISMapViewState( + stateId = stateId, + mapDesignType = mapDesign, + initCameraPosition = MapCameraPosition.from(cameraPosition), + ), + ) + } return state.value } @@ -171,4 +183,4 @@ internal fun Context.findActivity(): Activity? = is Activity -> this is ContextWrapper -> baseContext.findActivity() else -> null - } \ No newline at end of file + } diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/GeoPoint.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/GeoPoint.kt index 90639a92..dc57f4f3 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/GeoPoint.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/GeoPoint.kt @@ -5,38 +5,46 @@ import com.arcgismaps.geometry.Point import com.arcgismaps.geometry.SpatialReference import com.mapconductor.core.features.GeoPoint -fun GeoPoint.toPoint(spatialReference: SpatialReference = SpatialReference.wgs84()) : Point = +fun GeoPoint.toPoint(spatialReference: SpatialReference = SpatialReference.wgs84()): Point = Point(x = longitude, y = latitude, z = altitude, spatialReference = spatialReference) -fun GeoPoint.Companion.fromLatLongAltitude(latitude: Double, longitude: Double, altitude: Double) = - GeoPoint(latitude = latitude, longitude = longitude, altitude = altitude) +fun GeoPoint.Companion.fromLatLongAltitude( + latitude: Double, + longitude: Double, + altitude: Double, +) = GeoPoint(latitude = latitude, longitude = longitude, altitude = altitude) -fun GeoPoint.Companion.fromLongLat(longitude: Double, latitude: Double, altitude: Double) = - GeoPoint(latitude = latitude, longitude = longitude, altitude = altitude) +fun GeoPoint.Companion.fromLongLat( + longitude: Double, + latitude: Double, + altitude: Double, +) = GeoPoint(latitude = latitude, longitude = longitude, altitude = altitude) fun GeoPoint.Companion.from(point: Point) { - val wgs84Point = if (point.spatialReference != SpatialReference.wgs84()) { - GeometryEngine.projectOrNull(point, SpatialReference.wgs84()) as Point - } else { - point - } + val wgs84Point = + if (point.spatialReference != SpatialReference.wgs84()) { + GeometryEngine.projectOrNull(point, SpatialReference.wgs84()) as Point + } else { + point + } GeoPoint( longitude = wgs84Point.x, latitude = wgs84Point.y, - altitude = wgs84Point.z ?: 0.0 + altitude = wgs84Point.z ?: 0.0, ) } fun Point.toGeoPoint(): GeoPoint { - val wgs84Point = if (this.spatialReference != SpatialReference.wgs84()) { - GeometryEngine.projectOrNull(this, SpatialReference.wgs84()) as Point - } else { - this - } + val wgs84Point = + if (this.spatialReference != SpatialReference.wgs84()) { + GeometryEngine.projectOrNull(this, SpatialReference.wgs84()) as Point + } else { + this + } return GeoPoint( longitude = wgs84Point.x, latitude = wgs84Point.y, altitude = wgs84Point.z ?: 0.0, ) -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/IArcGISMapEventHandler.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/IArcGISMapEventHandler.kt index 80339d95..f439d42d 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/IArcGISMapEventHandler.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/IArcGISMapEventHandler.kt @@ -5,6 +5,8 @@ import com.mapconductor.core.marker.MarkerState interface IArcGISMapEventHandler { fun onCameraMove(cameraPosition: Camera) + fun onMarkerRemove(id: String) + fun onMarkerAdd(state: MarkerState) -} \ No newline at end of file +} diff --git a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/MapCameraPosition.kt b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/MapCameraPosition.kt index 003b6505..866d67d8 100644 --- a/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/MapCameraPosition.kt +++ b/mapconductor-for-arcgis/src/main/java/com/mapconductor/arcgis/MapCameraPosition.kt @@ -3,11 +3,11 @@ package com.mapconductor.arcgis import androidx.annotation.Keep import com.arcgismaps.geometry.Point import com.arcgismaps.mapping.view.Camera -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapPaddings -import com.mapconductor.core.MapPaddingsImpl import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.map.IMapCameraPosition +import com.mapconductor.core.map.MapPaddings +import com.mapconductor.core.map.MapPaddingsImpl import kotlin.math.PI import kotlin.math.asin import kotlin.math.atan2 @@ -17,29 +17,29 @@ import kotlin.math.max import kotlin.math.pow import kotlin.math.sin - -interface MapCameraPositionArcGIS: IMapCameraPosition { +interface MapCameraPositionArcGIS : IMapCameraPosition { fun toCamera(): Camera } -@Keep -data class MapCameraPosition @JvmOverloads constructor( - override val position: IGeoPoint, - override val zoom: Double = 2.0, - override val bearing: Double = 0.0, - override val tilt: Double = 0.0, - override val paddings: MapPaddings? = MapPaddingsImpl.Zeros, -): MapCameraPositionArcGIS { - - override fun toCamera(): Camera { - val targetPoint = GeoPoint.from(position).toPoint() - return calculateCameraForOrbitParameters( - targetPoint = targetPoint, - distance = zoomLevelToAltitude(zoom), - cameraHeadingOffset = 360 - (bearing + 180), - cameraPitchOffset = tilt, - ) - } +@Keep +data class MapCameraPosition + @JvmOverloads + constructor( + override val position: IGeoPoint, + override val zoom: Double = 2.0, + override val bearing: Double = 0.0, + override val tilt: Double = 0.0, + override val paddings: MapPaddings? = MapPaddingsImpl.Zeros, + ) : MapCameraPositionArcGIS { + override fun toCamera(): Camera { + val targetPoint = GeoPoint.from(position).toPoint() + return calculateCameraForOrbitParameters( + targetPoint = targetPoint, + distance = zoomLevelToAltitude(zoom), + cameraHeadingOffset = 360 - (bearing + 180), + cameraPitchOffset = tilt, + ) + } // // override fun copy( @@ -56,35 +56,33 @@ data class MapCameraPosition @JvmOverloads constructor( // paddings = paddings ?: this.paddings, // ) - companion object { - - fun from(position: IMapCameraPosition): MapCameraPosition { - return when(position) { - is MapCameraPosition -> position - else -> { + companion object { + fun from(position: IMapCameraPosition): MapCameraPosition = + when (position) { + is MapCameraPosition -> position + else -> { // val altitude = calculateZoomLevelFromScale( // positionImpl.zoom, // positionImpl.target.latitude, // Resources.getSystem().displayMetrics.densityDpi.toDouble(), // ) * 2.0 - val altitude = calculateScaleFromZoomLevel(position.zoom) - MapCameraPosition( - position = GeoPoint.fromLongLat( - longitude = position.position.longitude, - latitude = position.position.latitude, - altitude = altitude, - ), - zoom = position.zoom, - bearing = position.bearing, - tilt = position.tilt, - paddings = position.paddings, - ) + val altitude = calculateScaleFromZoomLevel(position.zoom) + MapCameraPosition( + position = + GeoPoint.fromLongLat( + longitude = position.position.longitude, + latitude = position.position.latitude, + altitude = altitude, + ), + zoom = position.zoom, + bearing = position.bearing, + tilt = position.tilt, + paddings = position.paddings, + ) + } } - } } } -} - /** * Google Maps の zoomLevel を基にArcGIS 用の scale を計算します。 @@ -94,12 +92,17 @@ data class MapCameraPosition @JvmOverloads constructor( * @param dpi ディスプレイのdpi(dots per inch)、通常は96 * @return 計算されたスケール(例: 1:scale の scale 部分) */ -fun calculateScaleFromZoomLevel(zoomLevel: Double, latitude: Double = 0.0, dpi: Double = 96.0): Double { +fun calculateScaleFromZoomLevel( + zoomLevel: Double, + latitude: Double = 0.0, + dpi: Double = 96.0, +): Double { // Google Maps の解像度(メートル/ピクセル) val resolution = (156543.03392 * Math.cos(Math.toRadians(latitude))) / (2.0.pow(zoomLevel)) // 1インチは0.0254メートル。scale = resolution × (dpi / 0.0254) return resolution * (dpi / 0.0254) } + /** * ArcGIS 用のscaleからGoogle Mapsの zoomLevel を近似計算します。 * @@ -108,17 +111,16 @@ fun calculateScaleFromZoomLevel(zoomLevel: Double, latitude: Double = 0.0, dpi: * @param dpi ディスプレイのdpi。通常は96dpiを使用します。 * @return 計算された zoomLevel (少数点以下の値) */ -fun calculateZoomLevelFromScale(scale: Double, latitude: Double = 0.0, dpi: Double = 96.0): Double { +fun calculateZoomLevelFromScale( + scale: Double, + latitude: Double = 0.0, + dpi: Double = 96.0, +): Double { // 定数部分: 156543.03392 * (dpi/0.0254) は、赤道上でのスケール0の時の値となる val constant = 156543.03392 * cos(Math.toRadians(latitude)) * (dpi / 0.0254) return log2(constant / scale) } - - - - - // 定数: この値は経験的に調整されることがあります。 // Webメルカトル図法の世界幅(ピクセル単位)と地球の円周(メートル単位)の関係から導出されることが多いです。 // 一般的なタイルサイズ(256px)と赤道半径に基づいた値の一例です。 @@ -128,22 +130,23 @@ private const val ALTITUDE_ZOOM_CONSTANT = 591657550.0 // Meters (approximate wo private const val MIN_ZOOM_LEVEL = 1.0 private const val MAX_ZOOM_LEVEL = 22.0 // Google Mapsは最大22程度までサポートすることが多い -private const val DEFAULT_MAX_Maps_TILT = 60.0 // Google Maps Tilt の想定最大値 +private const val DEFAULT_MAX_GMAPS_TILT = 60.0 // Google Maps Tilt の想定最大値 private const val ARCGIS_MAX_PITCH = 90.0 private const val MIN_ANGLE = 0.0 private const val EARTH_MEAN_RADIUS_METERS = 6371000.0 // 地球の平均半径 (より正確には楕円体を使うべき) + // --- 角度変換ヘルパー --- internal fun Double.toRadians(): Double = Math.toRadians(this) + internal fun Double.toDegrees(): Double = Math.toDegrees(this) // --- 以前の角度変換関数 --- -fun arcgisPitchToGoogleMapsTilt(pitch: Double, maxTilt: Double = DEFAULT_MAX_Maps_TILT): Double { - return pitch.coerceIn(MIN_ANGLE, maxTilt) -} +fun arcgisPitchToGoogleMapsTilt( + pitch: Double, + maxTilt: Double = DEFAULT_MAX_GMAPS_TILT, +): Double = pitch.coerceIn(MIN_ANGLE, maxTilt) -fun googleMapsTiltToArcgisPitch(tilt: Double): Double { - return tilt.coerceIn(MIN_ANGLE, ARCGIS_MAX_PITCH) -} +fun googleMapsTiltToArcgisPitch(tilt: Double): Double = tilt.coerceIn(MIN_ANGLE, ARCGIS_MAX_PITCH) // --- 以前の Altitude/Zoom 変換関数 (近似) --- fun altitudeToZoomLevel(altitude: Double): Double { @@ -170,7 +173,12 @@ fun zoomLevelToAltitude(zoomLevel: Double): Double { * @param distance 距離 (メートル) * @return Pair 新しい地点の緯度と経度 (度) */ -fun calculateDestinationPoint(lat: Double, lon: Double, bearing: Double, distance: Double): IGeoPoint { +fun calculateDestinationPoint( + lat: Double, + lon: Double, + bearing: Double, + distance: Double, +): IGeoPoint { val latRad = lat.toRadians() val lonRad = lon.toRadians() val bearingRad = bearing.toRadians() @@ -178,10 +186,12 @@ fun calculateDestinationPoint(lat: Double, lon: Double, bearing: Double, distanc val destLatRad = asin(sin(latRad) * cos(angularDistance) + cos(latRad) * sin(angularDistance) * cos(bearingRad)) - var destLonRad = lonRad + atan2( - sin(bearingRad) * sin(angularDistance) * cos(latRad), - cos(angularDistance) - sin(latRad) * sin(destLatRad) - ) + var destLonRad = + lonRad + + atan2( + sin(bearingRad) * sin(angularDistance) * cos(latRad), + cos(angularDistance) - sin(latRad) * sin(destLatRad), + ) // 経度を -180 ~ +180 の範囲に正規化 (atan2の結果による) destLonRad = (destLonRad + 3 * PI) % (2 * PI) - PI @@ -191,6 +201,7 @@ fun calculateDestinationPoint(lat: Double, lon: Double, bearing: Double, distanc override val altitude: Double? get() = null } } + /** * ArcGISの高度 (メートル単位) を Google Maps のズームレベル (近似値) に変換します。 * @@ -203,7 +214,7 @@ fun calculateDestinationPoint(lat: Double, lon: Double, bearing: Double, distanc fun altitudeToZoomLevel( altitude: Double, minZoom: Double = MIN_ZOOM_LEVEL, - maxZoom: Double = MAX_ZOOM_LEVEL + maxZoom: Double = MAX_ZOOM_LEVEL, ): Double { if (altitude <= 0) { // 高度が無効な場合は最小ズームレベルを返す @@ -229,7 +240,7 @@ fun altitudeToZoomLevel( fun zoomLevelToAltitude( zoomLevel: Double, minZoom: Double = MIN_ZOOM_LEVEL, - maxZoom: Double = MAX_ZOOM_LEVEL + maxZoom: Double = MAX_ZOOM_LEVEL, ): Double { // 入力ズームレベルを許容範囲内に制限する val clampedZoom = zoomLevel.coerceIn(minZoom, maxZoom) @@ -241,8 +252,6 @@ fun zoomLevelToAltitude( return altitude } - - /** * OrbitLocationCameraControllerで使用するパラメータから、 * 対応するArcGIS Cameraオブジェクトを計算します。 @@ -258,7 +267,7 @@ fun calculateCameraForOrbitParameters( targetPoint: Point, distance: Double, cameraHeadingOffset: Double, - cameraPitchOffset: Double + cameraPitchOffset: Double, ): Camera { // 1. カメラの最終的な Pitch は cameraPitchOffset と同じ val finalPitch = cameraPitchOffset.coerceIn(MIN_ANGLE, ARCGIS_MAX_PITCH) @@ -279,19 +288,21 @@ fun calculateCameraForOrbitParameters( // 5. カメラの位置 (Location) // ターゲット地点から、逆方位 (cameraHeadingOffset) に水平距離だけ離れた地点を計算 - val cameraCoordinates = calculateDestinationPoint( - lat = targetPoint.y, // latitude - lon = targetPoint.x, // longitude - bearing = cameraHeadingOffset, // ターゲットからカメラへの方位 - distance = horizontalDistance - ) + val cameraCoordinates = + calculateDestinationPoint( + lat = targetPoint.y, // latitude + lon = targetPoint.x, // longitude + bearing = cameraHeadingOffset, // ターゲットからカメラへの方位 + distance = horizontalDistance, + ) - val cameraLocation = Point( - x = cameraCoordinates.longitude, - y = cameraCoordinates.latitude, - z = altitude, - spatialReference = targetPoint.spatialReference, - ) + val cameraLocation = + Point( + x = cameraCoordinates.longitude, + y = cameraCoordinates.latitude, + z = altitude, + spatialReference = targetPoint.spatialReference, + ) // 6. 最終的なCameraオブジェクトを作成 return Camera( @@ -304,16 +315,19 @@ fun calculateCameraForOrbitParameters( ) } -fun Camera.toMapCameraPosition() = MapCameraPosition( - position = GeoPoint.fromLongLat( - longitude = this.location.x, - latitude = this.location.y, - altitude = this.location.z ?: 0.0, - ), - zoom = altitudeToZoomLevel( - altitude = this.location.z ?: 0.0, - ), +fun Camera.toMapCameraPosition() = + MapCameraPosition( + position = + GeoPoint.fromLongLat( + longitude = this.location.x, + latitude = this.location.y, + altitude = this.location.z ?: 0.0, + ), + zoom = + altitudeToZoomLevel( + altitude = this.location.z ?: 0.0, + ), bearing = 360 - this.heading, tilt = this.pitch, paddings = MapPaddingsImpl.Zeros, - ) \ No newline at end of file + ) diff --git a/mapconductor-for-arcgis/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt b/mapconductor-for-arcgis/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt index 51d633c9..e01a5dc8 100644 --- a/mapconductor-for-arcgis/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt +++ b/mapconductor-for-arcgis/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.mapconductor.googlemaps +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/mapconductor-for-googlemaps/build.gradle.kts b/mapconductor-for-googlemaps/build.gradle.kts index d04c61a9..18649ef4 100644 --- a/mapconductor-for-googlemaps/build.gradle.kts +++ b/mapconductor-for-googlemaps/build.gradle.kts @@ -3,6 +3,15 @@ plugins { id("org.jetbrains.kotlin.android") id("maven-publish") alias(libs.plugins.kotlin.compose) + id("org.jlleitschuh.gradle.ktlint") +} + +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } } android { @@ -29,7 +38,7 @@ android { isMinifyEnabled = project.property("isMinifyEnabled").toString().toBoolean() proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } @@ -60,4 +69,4 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file +} diff --git a/mapconductor-for-googlemaps/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt b/mapconductor-for-googlemaps/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt index 9f2e4d49..e1e339d8 100644 --- a/mapconductor-for-googlemaps/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt +++ b/mapconductor-for-googlemaps/src/androidTest/java/com/mapconductor/googlemaps/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.mapconductor.googlemaps -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.mapconductor.googlemaps", appContext.packageName) } -} \ No newline at end of file +} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoPoint.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoPoint.kt index b515eccc..b13d40d9 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoPoint.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GeoPoint.kt @@ -3,6 +3,8 @@ package com.mapconductor.googlemaps import com.google.android.gms.maps.model.LatLng import com.mapconductor.core.features.GeoPoint -fun GeoPoint.toLatLng() : LatLng = LatLng(latitude, longitude) +fun GeoPoint.toLatLng(): LatLng = LatLng(latitude, longitude) + fun GeoPoint.Companion.from(latLng: LatLng) = GeoPoint(latLng.latitude, latLng.longitude) + fun LatLng.toGeoPoint() = GeoPoint.fromLatLong(latitude, longitude) diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt index 5eea5959..873a17cf 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapDesign.kt @@ -5,28 +5,33 @@ import com.google.android.gms.maps.GoogleMap.MAP_TYPE_NONE import com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL import com.google.android.gms.maps.GoogleMap.MAP_TYPE_SATELLITE import com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN -import com.mapconductor.core.MapDesignType +import com.mapconductor.core.map.MapDesignType typealias GoogleMapDesignType = MapDesignType sealed class GoogleMapDesign( - override val id: Int -): GoogleMapDesignType { + override val id: Int, +) : GoogleMapDesignType { object Normal : GoogleMapDesign(MAP_TYPE_NORMAL) + object Satellite : GoogleMapDesign(MAP_TYPE_SATELLITE) + object Hybrid : GoogleMapDesign(MAP_TYPE_HYBRID) + object Terrain : GoogleMapDesign(MAP_TYPE_TERRAIN) + object None : GoogleMapDesign(MAP_TYPE_NONE) override fun getValue(): Int = id companion object { - fun Create(id: Int): GoogleMapDesign = when(id) { - Normal.id -> Normal - Satellite.id -> Satellite - Hybrid.id -> Hybrid - Terrain.id -> Terrain - else -> None - } + fun Create(id: Int): GoogleMapDesign = + when (id) { + Normal.id -> Normal + Satellite.id -> Satellite + Hybrid.id -> Hybrid + Terrain.id -> Terrain + else -> None + } } -} \ No newline at end of file +} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapExtension.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapExtension.kt deleted file mode 100644 index 118ca6cc..00000000 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapExtension.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.mapconductor.googlemaps - -//internal fun MarkerIconProp.toBitmapDescriptor() : BitmapDescriptor { -// return BitmapDescriptorFactory.fromBitmap(this.image) -//} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt index e39a5f0f..3b75d53c 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapView.kt @@ -1,6 +1,5 @@ package com.mapconductor.googlemaps -import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember @@ -14,6 +13,7 @@ import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.model.CameraPosition import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.map.MapViewBase +import android.view.ViewGroup @Composable fun GoogleMapsView( @@ -39,52 +39,63 @@ fun GoogleMapsView( onInitialize = { // Specific Google Maps initialization logic // This lambda will be executed within state.initAsync by MapViewBase - val cameraPosition = state.mapCameraPosition.value?.let { - CameraPosition.Builder().apply { - target(GeoPoint.from(it.position).toLatLng()) - zoom(it.zoom.toFloat()) - bearing(it.bearing.toFloat()) - tilt(it.tilt.toFloat()) - }.build() - } + val cameraPosition = + state.mapCameraPosition.value?.let { + CameraPosition + .Builder() + .apply { + target(GeoPoint.from(it.position).toLatLng()) + zoom(it.zoom.toFloat()) + bearing(it.bearing.toFloat()) + tilt(it.tilt.toFloat()) + }.build() + } - val mapInitOptions = GoogleMapOptions() - .mapType(state.mapDesignType.getValue()) - .camera(cameraPosition) + val mapInitOptions = + GoogleMapOptions() + .mapType(state.mapDesignType.getValue()) + .camera(cameraPosition) - val holder = GoogleMapViewHolderStore.getOrCreate( - context = context, // Use context from the outer scope - id = state.stateId, - options = mapInitOptions, - ) + val holder = + GoogleMapViewHolderStore.getOrCreate( + context = context, // Use context from the outer scope + id = state.stateId, + options = mapInitOptions, + ) val eventHandler = state as? IGoogleMapEventHandler - val controller = GoogleMapViewController( - holder = holder, - eventHandler = eventHandler, - ) + val controller = + GoogleMapViewController( + holder = holder, + eventHandler = eventHandler, + ) (state as? GoogleMapViewState)?.controller = controller holderRef.value = holder controllerRef.value = controller true // Return success/failure of initialization }, - customDisposableEffect = {_state, _holderRef -> + customDisposableEffect = { _state, _holderRef -> // Specific Google Maps DisposableEffect logic val lifecycle = LocalLifecycleOwner.current.lifecycle // Get lifecycle here DisposableEffect(lifecycle) { val stateId = _state.stateId - val observer = object : DefaultLifecycleObserver { - override fun onResume(owner: LifecycleOwner) {} - override fun onPause(owner: LifecycleOwner) {} - override fun onDestroy(owner: LifecycleOwner) { - val activity = context.findActivity() - if (activity?.isChangingConfigurations == true) { - (_holderRef.value!!.mapView.parent as? ViewGroup)?.removeView(_holderRef.value!!.mapView) - } else { - GoogleMapViewHolderStore.remove(stateId) + val observer = + object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) {} + + override fun onPause(owner: LifecycleOwner) {} + + override fun onDestroy(owner: LifecycleOwner) { + val activity = context.findActivity() + if (activity?.isChangingConfigurations == true) { + (_holderRef.value!!.mapView.parent as? ViewGroup)?.removeView( + _holderRef.value!!.mapView, + ) + } else { + GoogleMapViewHolderStore.remove(stateId) + } } } - } lifecycle.addObserver(observer) onDispose { _state.resetInitState() @@ -95,6 +106,6 @@ fun GoogleMapsView( // Pass content if it needs to be rendered within the overlay providers in MapViewBase, // or handle it here if it's specific to GoogleMapsView structure before calling MapViewBase. // For now, assuming content relates to overlay definitions. - content = content // This might need adjustment based on how overlays are handled + content = content, // This might need adjustment based on how overlays are handled ) } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt index d30a4424..e2fef2c7 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewController.kt @@ -22,60 +22,74 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.lang.ref.WeakReference -interface IGoogleMapViewController: MapViewController -{ - fun moveCamera(dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null) - fun animateCamera(dstPosition: MapCameraPosition, duration: Int, listener: MapViewState.MoveCameraCallback? = null) +interface IGoogleMapViewController : MapViewController { + fun moveCamera( + dstPosition: MapCameraPosition, + listener: MapViewState.MoveCameraCallback? = null, + ) + + fun animateCamera( + dstPosition: MapCameraPosition, + duration: Int, + listener: MapViewState.MoveCameraCallback? = null, + ) } class GoogleMapViewController( override val holder: GoogleMapViewHolder, eventHandler: IGoogleMapEventHandler?, override val coroutine: CoroutineScope = CoroutineScope(Dispatchers.Main), -): IGoogleMapViewController, +) : IGoogleMapViewController, OnCameraMoveStartedListener, OnCameraMoveCanceledListener, OnCameraMoveListener, OnCameraIdleListener, - OnMarkerClickListener -{ - private val baseMapViewController = BaseMapViewController( - coroutine = coroutine, - onMarkerRemove = { id, marker -> - marker.remove() - coroutine.launch { - eventHandlerRef.get()?.onMarkerRemove(id) - } - }, - onMarkerAdd = { newMarkers -> - newMarkers.map { params -> - val bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(params.icon.bitmap) - - val options = MarkerOptions() - .position(GeoPoint.from(params.entry.state.position).toLatLng()) - .anchor(params.icon.anchor.x.toFloat(), params.icon.anchor.y.toFloat()) - .icon(bitmapDescriptor) - .draggable(true) - val marker = holder.map.addMarker(options)?.also { - it.tag = params.entry.state.id + OnMarkerClickListener { + private val baseMapViewController = + BaseMapViewController( + coroutine = coroutine, + onMarkerRemove = { id, marker -> + marker.remove() + coroutine.launch { + eventHandlerRef.get()?.onMarkerRemove(id) } - return@map marker - } - }, - onMarkerChanged = { changes -> - changes.forEach { params -> - val bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(params.icon.bitmap) - params.marker.position = GeoPoint.from(params.entry.state.position).toLatLng() - params.marker.setIcon(bitmapDescriptor) - } - }, - ) + }, + onMarkerAdd = { newMarkers -> + newMarkers.map { params -> + val bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(params.icon.bitmap) + + val options = + MarkerOptions() + .position(GeoPoint.from(params.entry.state.position).toLatLng()) + .anchor( + params.icon.anchor.x + .toFloat(), + params.icon.anchor.y + .toFloat(), + ).icon(bitmapDescriptor) + .draggable(true) + val marker = + holder.map.addMarker(options)?.also { + it.tag = params.entry.state.id + } + return@map marker + } + }, + onMarkerChanged = { changes -> + changes.forEach { params -> + val bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(params.icon.bitmap) + params.marker.position = GeoPoint.from(params.entry.state.position).toLatLng() + params.marker.setIcon(bitmapDescriptor) + } + }, + ) private val eventHandlerRef = WeakReference(eventHandler) init { setupListeners() } + private fun setupListeners() { holder.map.setOnCameraMoveStartedListener(this) holder.map.setOnCameraMoveCanceledListener(this) @@ -104,27 +118,31 @@ class GoogleMapViewController( val dstCameraPosition = position.toCameraPosition() coroutine.launch { val cameraUpdate = CameraUpdateFactory.newCameraPosition(dstCameraPosition) - holder.map.animateCamera(cameraUpdate, duration, object : CancelableCallback { - override fun onCancel() { - listener?.onComplete(false) - } - - override fun onFinish() { - listener?.onComplete(true) - } - }) + holder.map.animateCamera( + cameraUpdate, + duration, + object : CancelableCallback { + override fun onCancel() { + listener?.onComplete(false) + } + + override fun onFinish() { + listener?.onComplete(true) + } + }, + ) } } - override suspend fun addMarkers(markerList : List) = - baseMapViewController.addMarkers(markerList) + override suspend fun addMarkers(markerList: List) = baseMapViewController.addMarkers(markerList) override suspend fun clearOverlays() = baseMapViewController.clearOverlays() override fun toScreenOffset(position: IGeoPoint): Offset? { - val point = holder.map.projection.toScreenLocation( - GeoPoint.from(position).toLatLng(), - ) + val point = + holder.map.projection.toScreenLocation( + GeoPoint.from(position).toLatLng(), + ) return Offset( x = point.x.toDouble(), y = point.y.toDouble(), @@ -166,4 +184,3 @@ class GoogleMapViewController( return true } } - diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderImpl.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderImpl.kt index 2166226b..605a2548 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderImpl.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderImpl.kt @@ -1,16 +1,16 @@ package com.mapconductor.googlemaps -import android.content.Context import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.MapView -import com.mapconductor.core.MapViewHolder +import com.mapconductor.core.map.MapViewHolder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine +import android.content.Context internal class GoogleMapViewHolderImpl private constructor( - override val mapView: MapView -): MapViewHolder { + override val mapView: MapView, +) : MapViewHolder { override lateinit var map: GoogleMap companion object { @@ -18,7 +18,6 @@ internal class GoogleMapViewHolderImpl private constructor( suspend fun create( context: Context, options: GoogleMapOptions? = null, - ): MapViewHolder { val mapView = MapView(context, options).apply { onCreate(null) } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderStore.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderStore.kt index b8755cda..75f56071 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderStore.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewHolderStore.kt @@ -1,13 +1,13 @@ package com.mapconductor.googlemaps -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.MapView -import com.mapconductor.core.MapViewHolder -import com.mapconductor.core.MapViewHolderStoreBaseAsync +import com.mapconductor.core.map.MapViewHolder +import com.mapconductor.core.map.MapViewHolderStoreBaseAsync +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper typealias GoogleMapViewHolder = MapViewHolder @@ -22,10 +22,11 @@ object GoogleMapViewHolderStore : MapViewHolderStoreBaseAsync this is ContextWrapper -> baseContext.findActivity() else -> null - } \ No newline at end of file + } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewScope.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewScope.kt index cbb94d46..c39d7afc 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewScope.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewScope.kt @@ -4,4 +4,4 @@ import com.mapconductor.core.map.MapViewScope class GoogleMapViewScope : MapViewScope() { // 他の地図SDKにはない機能(StreetViewなど)は、ここで定義する -} \ No newline at end of file +} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt index e8039fff..e53087c0 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/GoogleMapViewState.kt @@ -1,18 +1,17 @@ package com.mapconductor.googlemaps -import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import com.google.android.gms.maps.model.CameraPosition -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapCameraPositionBase -import com.mapconductor.core.MapPaddingsImpl import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.map.IMapCameraPosition import com.mapconductor.core.map.InitState +import com.mapconductor.core.map.MapCameraPositionBase +import com.mapconductor.core.map.MapPaddingsImpl import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.MapViewStateImpl import com.mapconductor.core.marker.MarkerState @@ -23,25 +22,25 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import java.util.UUID +import android.os.Bundle -interface IGoogleMapViewState: MapViewState +interface IGoogleMapViewState : MapViewState class GoogleMapViewState( override val stateId: String, override val mapDesignType: GoogleMapDesignType, override val initCameraPosition: MapCameraPosition, -): MapViewStateImpl(), IGoogleMapViewState, IGoogleMapEventHandler { - - +) : MapViewStateImpl(), + IGoogleMapViewState, + IGoogleMapEventHandler { // Map padding private val _padding = MutableStateFlow(MapPaddingsImpl.Zeros) val padding: StateFlow = _padding.asStateFlow() // Camera position - private val _cameraPosition = MutableStateFlow(initCameraPosition.toCameraPosition()) -// private val cameraPosition: StateFlow = _cameraPosition.asStateFlow() + private val cameraPosition = MutableStateFlow(initCameraPosition.toCameraPosition()) override val mapCameraPosition: StateFlow = - _cameraPosition.map { it.toMapCameraPosition(padding.value) }.stateIn( + cameraPosition.map { it.toMapCameraPosition(padding.value) }.stateIn( scope = mainCoroutine, started = SharingStarted.Eagerly, initialValue = null, @@ -49,7 +48,11 @@ class GoogleMapViewState( internal var controller: IGoogleMapViewController? = null - override fun moveCameraTo(position: IGeoPoint, durationMs: Long, listener: MapViewState.MoveCameraCallback?) { + override fun moveCameraTo( + position: IGeoPoint, + durationMs: Long, + listener: MapViewState.MoveCameraCallback?, + ) { if (this.isInitialized.value != InitState.Initialized) { this.warningLog("moveCameraTo() called before map is initialized.") listener?.onComplete(false) @@ -60,9 +63,10 @@ class GoogleMapViewState( listener?.onComplete(false) return } - val newPosition = currCameraPosition.copy( - position = position, - ) + val newPosition = + currCameraPosition.copy( + position = position, + ) this.moveCameraTo(newPosition, durationMs, listener) } @@ -88,15 +92,15 @@ class GoogleMapViewState( } override fun onCameraMoveStart(cameraPosition: CameraPosition) { - this._cameraPosition.value = cameraPosition + this.cameraPosition.value = cameraPosition } override fun onCameraMove(cameraPosition: CameraPosition) { - this._cameraPosition.value = cameraPosition + this.cameraPosition.value = cameraPosition } override fun onCameraMoveEnd(cameraPosition: CameraPosition) { - this._cameraPosition.value = cameraPosition + this.cameraPosition.value = cameraPosition } override fun onMarkerAdd(state: MarkerState) { @@ -106,57 +110,67 @@ class GoogleMapViewState( override fun onMarkerRemove(id: String) { // Do nothing here } - } -val GoogleMapViewStateSaver = Saver( - save = { state -> - val cameraStateBundle = state.mapCameraPosition.value.let { cameraState -> +val GoogleMapViewStateSaver = + Saver( + save = { state -> + val cameraStateBundle = + state.mapCameraPosition.value.let { cameraState -> + Bundle().apply { + putDouble("zoom", cameraState?.zoom ?: MapCameraPositionBase.Default.zoom) + putDouble("tilt", cameraState?.tilt ?: MapCameraPositionBase.Default.tilt) + putDouble("bearing", cameraState?.bearing ?: MapCameraPositionBase.Default.bearing) + putDouble( + "latitude", + cameraState?.position?.latitude + ?: MapCameraPositionBase.Default.position.latitude, + ) + putDouble( + "longitude", + cameraState?.position?.longitude + ?: MapCameraPositionBase.Default.position.longitude, + ) + } + } + + val mapDesignBundle = + Bundle().apply { + putInt("id", state.mapDesignType.id) + } + Bundle().apply { - putDouble("zoom", cameraState?.zoom ?: MapCameraPositionBase.Default.zoom) - putDouble("tilt", cameraState?.tilt ?: MapCameraPositionBase.Default.tilt) - putDouble("bearing", cameraState?.bearing ?: MapCameraPositionBase.Default.bearing) - putDouble("latitude", - cameraState?.position?.latitude ?: - MapCameraPositionBase.Default.position.latitude) - putDouble("longitude", - cameraState?.position?.longitude ?: - MapCameraPositionBase.Default.position.longitude) + putString("stateId", state.stateId) + putBundle("mapDesign", mapDesignBundle) + putBundle("camera", cameraStateBundle) } - } - - val mapDesignBundle = Bundle().apply { - putInt("id", state.mapDesignType.id) - } - - Bundle().apply { - putString("stateId", state.stateId) - putBundle("mapDesign", mapDesignBundle) - putBundle("camera", cameraStateBundle) - } - }, - restore = { storedData -> - val cameraBundle = storedData.getBundle("camera") - val mapDesignBundle = storedData.getBundle("mapDesign") - - GoogleMapViewState( - stateId = storedData.getString("stateId")!!, - mapDesignType = GoogleMapDesign.Create( - id = mapDesignBundle?.getInt("id") ?: GoogleMapDesign.Normal.id, - ), - initCameraPosition = MapCameraPosition( - position = GeoPoint.fromLatLong( - latitude = cameraBundle?.getDouble("latitude") ?: 0.0, - longitude = cameraBundle?.getDouble("longitude") ?: 0.0, - ), - zoom = cameraBundle?.getDouble("zoom") ?: 0.0, - bearing = cameraBundle?.getDouble("bearing") ?: 0.0, - tilt = cameraBundle?.getDouble("tilt") ?: 0.0, - paddings = null + }, + restore = { storedData -> + val cameraBundle = storedData.getBundle("camera") + val mapDesignBundle = storedData.getBundle("mapDesign") + + GoogleMapViewState( + stateId = storedData.getString("stateId")!!, + mapDesignType = + GoogleMapDesign.Create( + id = mapDesignBundle?.getInt("id") ?: GoogleMapDesign.Normal.id, + ), + initCameraPosition = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = cameraBundle?.getDouble("latitude") ?: 0.0, + longitude = cameraBundle?.getDouble("longitude") ?: 0.0, + ), + zoom = cameraBundle?.getDouble("zoom") ?: 0.0, + bearing = cameraBundle?.getDouble("bearing") ?: 0.0, + tilt = cameraBundle?.getDouble("tilt") ?: 0.0, + paddings = null, + ), ) - ) - }, -) + }, + ) + @Composable fun rememberGoogleMapViewState( mapDesign: GoogleMapDesignType = GoogleMapDesign.Normal, @@ -166,15 +180,18 @@ fun rememberGoogleMapViewState( val uuid = UUID.randomUUID().toString() mutableStateOf(uuid) } - val state = rememberSaveable( - stateSaver = GoogleMapViewStateSaver, - ) { - mutableStateOf(GoogleMapViewState( - stateId = stateId, - mapDesignType = mapDesign, - initCameraPosition = MapCameraPosition.from(cameraPosition), - )) - } + val state = + rememberSaveable( + stateSaver = GoogleMapViewStateSaver, + ) { + mutableStateOf( + GoogleMapViewState( + stateId = stateId, + mapDesignType = mapDesign, + initCameraPosition = MapCameraPosition.from(cameraPosition), + ), + ) + } return state.value } diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/IGoogleMapEventHandler.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/IGoogleMapEventHandler.kt index d0d35034..ba7f1760 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/IGoogleMapEventHandler.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/IGoogleMapEventHandler.kt @@ -5,8 +5,12 @@ import com.mapconductor.core.marker.MarkerState interface IGoogleMapEventHandler { fun onCameraMoveStart(cameraPosition: CameraPosition) + fun onCameraMove(cameraPosition: CameraPosition) + fun onCameraMoveEnd(cameraPosition: CameraPosition) + fun onMarkerAdd(state: MarkerState) + fun onMarkerRemove(id: String) -} \ No newline at end of file +} diff --git a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/MapCameraPosition.kt b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/MapCameraPosition.kt index a284b914..01769418 100644 --- a/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/MapCameraPosition.kt +++ b/mapconductor-for-googlemaps/src/main/java/com/mapconductor/googlemaps/MapCameraPosition.kt @@ -2,14 +2,14 @@ package com.mapconductor.googlemaps import androidx.annotation.Keep import com.google.android.gms.maps.model.CameraPosition -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapCameraPositionBase -import com.mapconductor.core.MapPaddings -import com.mapconductor.core.MapPaddingsImpl import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.map.IMapCameraPosition +import com.mapconductor.core.map.MapCameraPositionBase +import com.mapconductor.core.map.MapPaddings +import com.mapconductor.core.map.MapPaddingsImpl -interface MapCameraPositionGMaps: IMapCameraPosition { +interface MapCameraPositionGMaps : IMapCameraPosition { fun toCameraPosition(): CameraPosition } @@ -20,10 +20,11 @@ data class MapCameraPosition( override val bearing: Double = 0.0, override val tilt: Double = 0.0, override val paddings: MapPaddings? = MapPaddingsImpl.Zeros, -): MapCameraPositionBase(position, zoom, bearing, tilt, paddings), MapCameraPositionGMaps { - +) : MapCameraPositionBase(position, zoom, bearing, tilt, paddings), + MapCameraPositionGMaps { override fun toCameraPosition() = - CameraPosition.builder() + CameraPosition + .builder() .target(GeoPoint.from(position).toLatLng()) .zoom(zoom.toFloat()) .tilt(tilt.toFloat()) @@ -31,23 +32,23 @@ data class MapCameraPosition( .build() companion object { - - fun from(position: IMapCameraPosition) = when(position) { - is MapCameraPosition -> position - else -> MapCameraPosition( - position = GeoPoint.from(position.position), - zoom = position.zoom, - bearing = position.bearing, - tilt = position.tilt, - paddings = position.paddings, - ) - } + fun from(position: IMapCameraPosition) = + when (position) { + is MapCameraPosition -> position + else -> + MapCameraPosition( + position = GeoPoint.from(position.position), + zoom = position.zoom, + bearing = position.bearing, + tilt = position.tilt, + paddings = position.paddings, + ) + } } } -fun CameraPosition.toMapCameraPosition( - paddings: MapPaddings = MapPaddingsImpl.Zeros, -) = MapCameraPosition( +fun CameraPosition.toMapCameraPosition(paddings: MapPaddings = MapPaddingsImpl.Zeros) = + MapCameraPosition( position = target.toGeoPoint(), zoom = zoom.toDouble(), bearing = bearing.toDouble(), diff --git a/mapconductor-for-googlemaps/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt b/mapconductor-for-googlemaps/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt index 51d633c9..e01a5dc8 100644 --- a/mapconductor-for-googlemaps/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt +++ b/mapconductor-for-googlemaps/src/test/java/com/mapconductor/googlemaps/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.mapconductor.googlemaps +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/mapconductor-for-here/build.gradle.kts b/mapconductor-for-here/build.gradle.kts index 675e73f7..1eddb4b4 100644 --- a/mapconductor-for-here/build.gradle.kts +++ b/mapconductor-for-here/build.gradle.kts @@ -3,6 +3,15 @@ plugins { id("org.jetbrains.kotlin.android") id("maven-publish") alias(libs.plugins.kotlin.compose) // ← 任意(配布したい場合) + id("org.jlleitschuh.gradle.ktlint") +} + +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } } android { @@ -29,7 +38,7 @@ android { isMinifyEnabled = project.property("isMinifyEnabled").toString().toBoolean() proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } @@ -60,4 +69,4 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file +} diff --git a/mapconductor-for-here/src/androidTest/java/com/mapconductor/here/ExampleInstrumentedTest.kt b/mapconductor-for-here/src/androidTest/java/com/mapconductor/here/ExampleInstrumentedTest.kt index baa6b734..24777eac 100644 --- a/mapconductor-for-here/src/androidTest/java/com/mapconductor/here/ExampleInstrumentedTest.kt +++ b/mapconductor-for-here/src/androidTest/java/com/mapconductor/here/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.mapconductor.here -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.mapconductor.googlemaps", appContext.packageName) } -} \ No newline at end of file +} diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/BitmapIcon.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/BitmapIcon.kt index 31ef7bed..c5d2c765 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/BitmapIcon.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/BitmapIcon.kt @@ -5,14 +5,12 @@ import com.here.sdk.mapview.ImageFormat import com.here.sdk.mapview.MapImage import com.mapconductor.core.marker.BitmapIcon -internal fun BitmapIcon.toMapImage(): MapImage { - return MapImage( +internal fun BitmapIcon.toMapImage(): MapImage = + MapImage( this.toByteArray(), ImageFormat.PNG, this.bitmap.width.toLong(), this.bitmap.height.toLong(), ) -} -internal fun BitmapIcon.toAnchor2D() : Anchor2D { - return Anchor2D(this.anchor.x.toDouble(), this.anchor.y.toDouble()) -} \ No newline at end of file + +internal fun BitmapIcon.toAnchor2D(): Anchor2D = Anchor2D(this.anchor.x.toDouble(), this.anchor.y.toDouble()) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/GeoPoint.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/GeoPoint.kt index 4c6a1672..2fce8af4 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/GeoPoint.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/GeoPoint.kt @@ -6,8 +6,13 @@ import com.here.sdk.core.GeoOrientation import com.here.sdk.core.GeoOrientationUpdate import com.mapconductor.core.features.GeoPoint -fun GeoPoint.toGeoCoordinates() : GeoCoordinates = GeoCoordinates(latitude, longitude) -fun GeoPoint.Companion.from(geoCoordinates: GeoCoordinates) = GeoPoint(geoCoordinates.latitude, geoCoordinates.longitude) +fun GeoPoint.toGeoCoordinates(): GeoCoordinates = GeoCoordinates(latitude, longitude) + +fun GeoPoint.Companion.from(geoCoordinates: GeoCoordinates) = + GeoPoint(geoCoordinates.latitude, geoCoordinates.longitude) + fun GeoCoordinates.toGeoPoint() = GeoPoint.fromLatLong(latitude, longitude) + fun GeoCoordinates.toUpdate() = GeoCoordinatesUpdate(this) -fun GeoOrientation.toUpdate() = GeoOrientationUpdate(this) \ No newline at end of file + +fun GeoOrientation.toUpdate() = GeoOrientationUpdate(this) diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapController.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapController.kt index 1388d641..dec830bb 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapController.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapController.kt @@ -13,7 +13,6 @@ import com.here.sdk.mapview.MapMarker import com.here.sdk.mapview.MapMeasure import com.here.sdk.mapview.MapView import com.here.time.Duration -import com.mapconductor.core.MapViewHolder import com.mapconductor.core.Offset import com.mapconductor.core.calculateZIndex import com.mapconductor.core.controller.BaseMapViewController @@ -21,6 +20,7 @@ import com.mapconductor.core.controller.MapViewController import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint import com.mapconductor.core.geocell.HexGeocell +import com.mapconductor.core.map.MapViewHolder import com.mapconductor.core.map.MapViewState.MoveCameraCallback import com.mapconductor.core.marker.MarkerEntry import com.mapconductor.core.projection.WebMercator @@ -31,64 +31,76 @@ import kotlinx.coroutines.launch import java.lang.ref.WeakReference import kotlin.math.pow -interface IHereMapViewController: MapViewController { - fun moveCamera(dstPosition: MapCameraPosition, listener: MoveCameraCallback? = null) - fun animateCamera(dstPosition: MapCameraPosition, durationMs: Long, listener: MoveCameraCallback? = null) +interface IHereMapViewController : MapViewController { + fun moveCamera( + dstPosition: MapCameraPosition, + listener: MoveCameraCallback? = null, + ) + + fun animateCamera( + dstPosition: MapCameraPosition, + durationMs: Long, + listener: MoveCameraCallback? = null, + ) } + internal class HereMapController( override val holder: MapViewHolder, eventHandler: IHereMapEventHandler?, override val coroutine: CoroutineScope = CoroutineScope(Dispatchers.Default), ) : IHereMapViewController, MapCameraListener, - TapListener -{ - - private val baseMapViewController = BaseMapViewController( - geocell = HexGeocell(WebMercator, 1), - coroutine = coroutine, - onMarkerRemove = { id, marker -> - holder.mapView.mapScene.removeMapMarker(marker) - coroutine.launch { - eventHandlerRef.get()?.onMarkerRemove(id) - } - }, - onMarkerAdd = { newMarkers -> - val markers = newMarkers.map { params -> - val marker = MapMarker( - GeoPoint.from(params.entry.state.position).toGeoCoordinates(), - params.icon.toMapImage(), - params.icon.toAnchor2D(), - ).apply { - drawOrder = calculateZIndex(params.entry.state.position).toInt() - metadata = Metadata().apply { - setString("id", params.entry.state.id) - } + TapListener { + private val baseMapViewController = + BaseMapViewController( + geocell = HexGeocell(WebMercator, 1), + coroutine = coroutine, + onMarkerRemove = { id, marker -> + holder.mapView.mapScene.removeMapMarker(marker) + coroutine.launch { + eventHandlerRef.get()?.onMarkerRemove(id) } - return@map marker - } + }, + onMarkerAdd = { newMarkers -> + val markers = + newMarkers.map { params -> + val marker = + MapMarker( + GeoPoint.from(params.entry.state.position).toGeoCoordinates(), + params.icon.toMapImage(), + params.icon.toAnchor2D(), + ).apply { + drawOrder = calculateZIndex(params.entry.state.position).toInt() + metadata = + Metadata().apply { + setString("id", params.entry.state.id) + } + } + return@map marker + } - holder.mapView.mapScene.addMapMarkers(markers) - return@BaseMapViewController markers - }, - onMarkerChanged = { changes -> - changes.forEach { params -> - // TODO: アイコンに変更があったかどうかを比較 - params.marker.image = params.icon.toMapImage() - params.marker.coordinates = GeoPoint.from(params.entry.state.position).toGeoCoordinates() - params.marker.anchor = params.icon.toAnchor2D() - } - }, - ) + holder.mapView.mapScene.addMapMarkers(markers) + return@BaseMapViewController markers + }, + onMarkerChanged = { changes -> + changes.forEach { params -> + // TODO: アイコンに変更があったかどうかを比較 + params.marker.image = params.icon.toMapImage() + params.marker.coordinates = GeoPoint.from(params.entry.state.position).toGeoCoordinates() + params.marker.anchor = params.icon.toAnchor2D() + } + }, + ) override suspend fun addMarkers(markerList: List) = baseMapViewController.addMarkers(markerList) override suspend fun clearOverlays() = baseMapViewController.clearOverlays() override fun toScreenOffset(position: IGeoPoint): Offset? { - val result = holder.mapView.geoToViewCoordinates( - GeoPoint.from(position).toGeoCoordinates(), - ) ?: return null + val result = + holder.mapView.geoToViewCoordinates( + GeoPoint.from(position).toGeoCoordinates(), + ) ?: return null return Offset( x = result.x, @@ -110,7 +122,7 @@ internal class HereMapController( override fun moveCamera( dstPosition: MapCameraPosition, - listener: MoveCameraCallback? + listener: MoveCameraCallback?, ) { val camera = this.holder.mapView.camera camera.applyUpdate( @@ -122,7 +134,7 @@ internal class HereMapController( override fun animateCamera( dstPosition: MapCameraPosition, durationMs: Long, - listener: MoveCameraCallback? + listener: MoveCameraCallback?, ) { val camera = this.holder.mapView.camera @@ -130,13 +142,14 @@ internal class HereMapController( // bowFactor < 0: 最初にズームイン → 到達時にズームアウト(ややレア) // bowFactor = 0: 常に同じズーム(直線的) val bowFactor = 1.0 - val animation = MapCameraAnimationFactory.flyTo( - GeoPoint.from(dstPosition.position).toGeoCoordinates().toUpdate(), - GeoOrientation(dstPosition.bearing, dstPosition.tilt).toUpdate(), - MapMeasure(MapMeasure.Kind.ZOOM_LEVEL, dstPosition.zoom), - bowFactor, - Duration.ofMillis(durationMs), - ) + val animation = + MapCameraAnimationFactory.flyTo( + GeoPoint.from(dstPosition.position).toGeoCoordinates().toUpdate(), + GeoOrientation(dstPosition.bearing, dstPosition.tilt).toUpdate(), + MapMeasure(MapMeasure.Kind.ZOOM_LEVEL, dstPosition.zoom), + bowFactor, + Duration.ofMillis(durationMs), + ) coroutine.launch { camera.startAnimation(animation) { animState -> when (animState) { @@ -185,7 +198,6 @@ internal class HereMapController( } } - // baseMapViewController.findMarker(touchPosition, 32.0 * holder.mapView.context.resources.displayMetrics.density, zoom)?.also { entry -> // entry.handlers.onClick?.let { // coroutine.launch { @@ -223,22 +235,22 @@ internal class HereMapController( // // TODO: find tapped overlay (do not remove this comment) // }) } - private fun zoomToIdPrefixLevel(zoom: Double): Int { - return when { - zoom <= 5 -> 2 // 数10km 単位でまとめる - zoom <= 8 -> 3 // 数km 単位 - zoom <= 10 -> 4 // 500m ~ 1km - zoom <= 12 -> 5 // 100~300m - zoom <= 14 -> 6 // 50~100m - zoom <= 16 -> 7 // 20~50m - zoom <= 18 -> 8 // 5~20m - else -> 9 // ~1mまで細分化 + + private fun zoomToIdPrefixLevel(zoom: Double): Int = + when { + zoom <= 5 -> 2 // 数10km 単位でまとめる + zoom <= 8 -> 3 // 数km 単位 + zoom <= 10 -> 4 // 500m ~ 1km + zoom <= 12 -> 5 // 100~300m + zoom <= 14 -> 6 // 50~100m + zoom <= 16 -> 7 // 20~50m + zoom <= 18 -> 8 // 5~20m + else -> 9 // ~1mまで細分化 } - } private fun hereZoomToMetersPerPixel(zoom: Double): Double { val earthCircumference = 40075016.686 val tileSize = 256 return earthCircumference / (tileSize * 2.0.pow(zoom)) } -} \ No newline at end of file +} diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt index a9d247ba..da707ea6 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapDesign.kt @@ -1,64 +1,80 @@ package com.mapconductor.here import com.here.sdk.mapview.MapScheme -import com.mapconductor.core.MapDesignType +import com.mapconductor.core.map.MapDesignType typealias HereMapDesignType = MapDesignType sealed class HereMapDesign( override val id: MapScheme, -): HereMapDesignType { +) : HereMapDesignType { object NormalDay : HereMapDesign(MapScheme.NORMAL_DAY) // 通常の昼モード + object NormalNigh : HereMapDesign(MapScheme.NORMAL_NIGHT) // 通常の夜モード + object Satellite : HereMapDesign(MapScheme.SATELLITE) // サテライト(衛星写真)モード + object HybridDay : HereMapDesign(MapScheme.HYBRID_DAY) // サテライト+道路情報(昼) + object HybridNight : HereMapDesign(MapScheme.HYBRID_NIGHT) // サテライト+道路情報(夜) + object LiteDay : HereMapDesign(MapScheme.LITE_DAY) // ライト(軽量)昼モード + object LiteNight : HereMapDesign(MapScheme.LITE_NIGHT) // ライト(軽量)夜モード + object LiteHybridDay : HereMapDesign(MapScheme.LITE_HYBRID_DAY) // ライトハイブリッド昼モード + object LiteHybridNight : HereMapDesign(MapScheme.LITE_HYBRID_NIGHT) // ライトハイブリッド夜モード + object LogisticsDay : HereMapDesign(MapScheme.LOGISTICS_DAY) // 物流向け昼モード + object LogisticsNight : HereMapDesign(MapScheme.LOGISTICS_NIGHT) + object LogisticsHybridDay : HereMapDesign(MapScheme.LOGISTICS_HYBRID_DAY) + object RoadNetworkDay : HereMapDesign(MapScheme.ROAD_NETWORK_DAY) + object RoadNetworkNight : HereMapDesign(MapScheme.ROAD_NETWORK_NIGHT) override fun getValue(): MapScheme = id companion object { - fun CreateById(id: Int): HereMapDesign = when(id) { - NormalDay.id.value -> NormalDay - NormalNigh.id.value -> NormalNigh - Satellite.id.value -> Satellite - HybridDay.id.value -> HybridDay - HybridNight.id.value -> HybridNight - LiteDay.id.value -> LiteDay - LiteNight.id.value -> LiteNight - LiteHybridDay.id.value -> LiteHybridDay - LiteHybridNight.id.value -> LiteHybridNight - LogisticsDay.id.value -> LogisticsDay - LogisticsNight.id.value -> LogisticsNight - LogisticsHybridDay.id.value -> LogisticsHybridDay - RoadNetworkDay.id.value -> RoadNetworkDay - RoadNetworkNight.id.value -> RoadNetworkNight - else -> throw IllegalArgumentException("Unsupported MapScene : ${id}") - } - fun Create(id: MapScheme): HereMapDesign = when(id) { - NormalDay.id -> NormalDay - NormalNigh.id -> NormalNigh - Satellite.id -> Satellite - HybridDay.id -> HybridDay - HybridNight.id -> HybridNight - LiteDay.id -> LiteDay - LiteNight.id -> LiteNight - LiteHybridDay.id -> LiteHybridDay - LiteHybridNight.id -> LiteHybridNight - LogisticsDay.id -> LogisticsDay - LogisticsNight.id -> LogisticsNight - LogisticsHybridDay.id -> LogisticsHybridDay - RoadNetworkDay.id -> RoadNetworkDay - RoadNetworkNight.id -> RoadNetworkNight - else -> throw IllegalArgumentException("Unsupported MapScene : ${id}") - } + fun CreateById(id: Int): HereMapDesign = + when (id) { + NormalDay.id.value -> NormalDay + NormalNigh.id.value -> NormalNigh + Satellite.id.value -> Satellite + HybridDay.id.value -> HybridDay + HybridNight.id.value -> HybridNight + LiteDay.id.value -> LiteDay + LiteNight.id.value -> LiteNight + LiteHybridDay.id.value -> LiteHybridDay + LiteHybridNight.id.value -> LiteHybridNight + LogisticsDay.id.value -> LogisticsDay + LogisticsNight.id.value -> LogisticsNight + LogisticsHybridDay.id.value -> LogisticsHybridDay + RoadNetworkDay.id.value -> RoadNetworkDay + RoadNetworkNight.id.value -> RoadNetworkNight + else -> throw IllegalArgumentException("Unsupported MapScene : $id") + } + + fun Create(id: MapScheme): HereMapDesign = + when (id) { + NormalDay.id -> NormalDay + NormalNigh.id -> NormalNigh + Satellite.id -> Satellite + HybridDay.id -> HybridDay + HybridNight.id -> HybridNight + LiteDay.id -> LiteDay + LiteNight.id -> LiteNight + LiteHybridDay.id -> LiteHybridDay + LiteHybridNight.id -> LiteHybridNight + LogisticsDay.id -> LogisticsDay + LogisticsNight.id -> LogisticsNight + LogisticsHybridDay.id -> LogisticsHybridDay + RoadNetworkDay.id -> RoadNetworkDay + RoadNetworkNight.id -> RoadNetworkNight + else -> throw IllegalArgumentException("Unsupported MapScene : $id") + } } -} \ No newline at end of file +} diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapExtension.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapExtension.kt deleted file mode 100644 index d8cccb4e..00000000 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapExtension.kt +++ /dev/null @@ -1,2 +0,0 @@ -package com.mapconductor.here - diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt index 8d392fc6..ee7f3e69 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapView.kt @@ -1,7 +1,5 @@ package com.mapconductor.here -import android.util.Log -import android.view.ViewGroup import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember @@ -15,6 +13,8 @@ import com.mapconductor.core.map.MapViewBase import com.mapconductor.core.map.MapViewState import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.suspendCancellableCoroutine +import android.util.Log +import android.view.ViewGroup @OptIn(ExperimentalCoroutinesApi::class) @Composable @@ -42,26 +42,28 @@ fun HereMapView( onInitialize = { HereMapViewHolderStore.initSDK(context) - val mapInitOptions = HereMapViewInitOptions( - scheme = state.mapDesignType.id, - ) + val mapInitOptions = + HereMapViewInitOptions( + scheme = state.mapDesignType.id, + ) - val holder = HereMapViewHolderStore.getOrCreate( - context = context, - id = state.stateId, - options = mapInitOptions, - ) + val holder = + HereMapViewHolderStore.getOrCreate( + context = context, + id = state.stateId, + options = mapInitOptions, + ) // Cast state if it implements event handlers val eventHandler = state as? IHereMapEventHandler - val controller = HereMapController( - holder = holder, - eventHandler = eventHandler, - ) + val controller = + HereMapController( + holder = holder, + eventHandler = eventHandler, + ) (state as? HereMapViewState)?.controller = controller - holder.mapView.mapScene.loadScene(state.mapDesignType.id) { mapError -> if (mapError != null) { throw Throwable("Loading map failed: mapError: " + mapError.name) @@ -75,11 +77,12 @@ fun HereMapView( val restoreCameraPosition = state.mapCameraPosition.value ?: state.initCameraPosition controller.moveCamera( dstPosition = MapCameraPosition.from(restoreCameraPosition), - listener = object : MapViewState.MoveCameraCallback { - override fun onComplete(result: Boolean) { - cont.resume(result) { } - } - } + listener = + object : MapViewState.MoveCameraCallback { + override fun onComplete(result: Boolean) { + cont.resume(result) { } + } + }, ) } } catch (e: Exception) { @@ -87,36 +90,38 @@ fun HereMapView( false // Scene loading failed } }, - customDisposableEffect = { _state, _holderRef -> // HERE specific DisposableEffect logic DisposableEffect(lifecycle) { val stateId = _state.stateId // from BaseMapViewState - val observer = object : DefaultLifecycleObserver { - override fun onResume(owner: LifecycleOwner) { - // Do not call here to keep the MapView instance - // _holderRef.value?.mapView?.onResume() - } - override fun onPause(owner: LifecycleOwner) { - // Do not call here to keep the MapView instance - // _holderRef.value?.mapView?.onPause() - } - override fun onDestroy(owner: LifecycleOwner) { - val currentHolder = _holderRef.value - if (currentHolder != null) { - val activity = context.findActivity() - if (activity?.isChangingConfigurations == true) { - (currentHolder.mapView.parent as? ViewGroup)?.removeView(currentHolder.mapView) - } else { - // Ensure these calls are safe if mapView might be null or already destroyed - currentHolder.mapView.onPause() - currentHolder.mapView.onDestroy() - HereMapViewHolderStore.remove(stateId) // Clean up from your store + val observer = + object : DefaultLifecycleObserver { + override fun onResume(owner: LifecycleOwner) { + // Do not call here to keep the MapView instance + // _holderRef.value?.mapView?.onResume() + } + + override fun onPause(owner: LifecycleOwner) { + // Do not call here to keep the MapView instance + // _holderRef.value?.mapView?.onPause() + } + + override fun onDestroy(owner: LifecycleOwner) { + val currentHolder = _holderRef.value + if (currentHolder != null) { + val activity = context.findActivity() + if (activity?.isChangingConfigurations == true) { + (currentHolder.mapView.parent as? ViewGroup)?.removeView(currentHolder.mapView) + } else { + // Ensure these calls are safe if mapView might be null or already destroyed + currentHolder.mapView.onPause() + currentHolder.mapView.onDestroy() + HereMapViewHolderStore.remove(stateId) // Clean up from your store + } } } } - } lifecycle.addObserver(observer) onDispose { _state.resetInitState() @@ -127,6 +132,6 @@ fun HereMapView( // Pass content if it needs to be rendered within the overlay providers in MapViewBase, // or handle it here if it's specific to GoogleMapsView structure before calling MapViewBase. // For now, assuming content relates to overlay definitions. - content = content // This might need adjustment based on how overlays are handled + content = content, // This might need adjustment based on how overlays are handled ) } diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderImpl.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderImpl.kt index ba5d0a71..2e369587 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderImpl.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderImpl.kt @@ -1,32 +1,30 @@ package com.mapconductor.here -import android.content.Context import com.here.sdk.mapview.HereMap import com.here.sdk.mapview.MapRenderMode import com.here.sdk.mapview.MapView import com.here.sdk.mapview.MapViewOptions -import com.mapconductor.core.MapViewHolder +import com.mapconductor.core.map.MapViewHolder +import android.content.Context internal class HereMapViewHolderImpl private constructor( override val mapView: MapView, -): MapViewHolder { +) : MapViewHolder { override lateinit var map: HereMap companion object { - - fun create( - context: Context, - ): MapViewHolder { - + fun create(context: Context): MapViewHolder { // TEXTUREモードにしないとデバイスが回転したときに再描画を適切に行わない - val viewOptions = MapViewOptions().also { - it.renderMode = MapRenderMode.TEXTURE - } + val viewOptions = + MapViewOptions().also { + it.renderMode = MapRenderMode.TEXTURE + } - val mapView = MapView(context, viewOptions).apply { - onCreate(null) - onResume() - } + val mapView = + MapView(context, viewOptions).apply { + onCreate(null) + onResume() + } val holder = HereMapViewHolderImpl(mapView) holder.map = mapView.hereMap diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderStore.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderStore.kt index 3a0e3714..9eb3d970 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderStore.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/HereMapViewHolderStore.kt @@ -1,19 +1,18 @@ package com.mapconductor.here -import android.content.Context -import android.content.pm.PackageManager import com.here.sdk.core.engine.AuthenticationMode import com.here.sdk.core.engine.SDKNativeEngine import com.here.sdk.core.engine.SDKOptions import com.here.sdk.mapview.HereMap import com.here.sdk.mapview.MapView -import com.mapconductor.core.MapViewHolder -import com.mapconductor.core.MapViewHolderStoreBaseAsync +import com.mapconductor.core.map.MapViewHolder +import com.mapconductor.core.map.MapViewHolderStoreBaseAsync +import android.content.Context +import android.content.pm.PackageManager typealias HereMapViewHolder = MapViewHolder object HereMapViewHolderStore : MapViewHolderStoreBaseAsync() { - private var mapCount: Int = 0 fun initSDK(context: Context) { @@ -24,17 +23,22 @@ object HereMapViewHolderStore : MapViewHolderStoreBaseAsync is required" - ) - if (accessKeySecret == null) throw Exception( - " is required" - ) + if (accessKeyId == null) { + throw Exception( + " is required", + ) + } + if (accessKeySecret == null) { + throw Exception( + " is required", + ) + } - val authenticationMode = AuthenticationMode.withKeySecret( - accessKeyId, - accessKeySecret, - ) + val authenticationMode = + AuthenticationMode.withKeySecret( + accessKeyId, + accessKeySecret, + ) val sdkOption = SDKOptions(authenticationMode) SDKNativeEngine.makeSharedInstance(context.applicationContext, sdkOption) this.mapCount++ @@ -43,7 +47,6 @@ object HereMapViewHolderStore : MapViewHolderStoreBaseAsync @@ -90,9 +94,13 @@ object HereMapViewHolderStore : MapViewHolderStoreBaseAsync +interface IHereMapViewState : MapViewState class HereMapViewState( override val stateId: String, override val mapDesignType: HereMapDesignType, override val initCameraPosition: MapCameraPosition = MapCameraPosition.Default, -) : MapViewStateImpl(), IHereMapViewState, IHereMapEventHandler { - +) : MapViewStateImpl(), + IHereMapViewState, + IHereMapEventHandler { internal var controller: IHereMapViewController? = null // Camera center position - private val _cameraPosition = MutableStateFlow(null) + private val cameraPosition = MutableStateFlow(null) override val mapCameraPosition: StateFlow = - _cameraPosition.map { it?.toMapCameraPosition() }.stateIn( + cameraPosition.map { it?.toMapCameraPosition() }.stateIn( scope = mainCoroutine, started = SharingStarted.Eagerly, initialValue = null, @@ -62,9 +63,10 @@ class HereMapViewState( listener?.onComplete(false) return } - val newPosition = currCameraPosition.copy( - position = position, - ) + val newPosition = + currCameraPosition.copy( + position = position, + ) this.moveCameraTo(newPosition, durationMs, listener) } @@ -93,7 +95,7 @@ class HereMapViewState( } override fun onCameraMove(cameraState: MapCamera.State) { - this._cameraPosition.value = cameraState + this.cameraPosition.value = cameraState } override fun onMarkerRemove(id: String) { @@ -103,54 +105,58 @@ class HereMapViewState( override fun onMarkerAdd(state: MarkerState) { // Do nothing here } - } -val HereMapViewStateSaver = Saver( - save = { state -> - val cameraStateBundle = state.mapCameraPosition.value?.let { cameraState -> +val HereMapViewStateSaver = + Saver( + save = { state -> + val cameraStateBundle = + state.mapCameraPosition.value?.let { cameraState -> + Bundle().apply { + putDouble("zoom", cameraState.zoom) + putDouble("tilt", cameraState.tilt) + putDouble("bearing", cameraState.bearing) + putDouble("latitude", cameraState.position.latitude) + putDouble("longitude", cameraState.position.longitude) + } + } + + val mapDesignBundle = + Bundle().apply { + putInt("id", state.mapDesignType.getValue().value) + } + Bundle().apply { - putDouble("zoom", cameraState.zoom) - putDouble("tilt", cameraState.tilt) - putDouble("bearing", cameraState.bearing) - putDouble("latitude", cameraState.position.latitude) - putDouble("longitude", cameraState.position.longitude) + putString("stateId", state.stateId) + putBundle("mapDesign", mapDesignBundle) + putBundle("camera", cameraStateBundle) } - } - - val mapDesignBundle = Bundle().apply { - putInt("id", state.mapDesignType.getValue().value) - } - - Bundle().apply { - putString("stateId", state.stateId) - putBundle("mapDesign", mapDesignBundle) - putBundle("camera", cameraStateBundle) - } - }, - restore = { storedData -> - val cameraBundle = storedData.getBundle("camera") - val mapDesignBundle = storedData.getBundle("mapDesign") - - HereMapViewState( - stateId = storedData.getString("stateId")!!, - mapDesignType = HereMapDesign.CreateById( - id = mapDesignBundle?.getInt("id") ?: HereMapDesign.NormalDay.id.value, - ), - initCameraPosition = MapCameraPosition( - position = GeoPoint.fromLatLong( - latitude = cameraBundle?.getDouble("latitude") ?: 0.0, - longitude = cameraBundle?.getDouble("longitude") ?: 0.0, - ), - zoom = cameraBundle?.getDouble("zoom") ?: 0.0, - bearing = cameraBundle?.getDouble("bearing") ?: 0.0, - tilt = cameraBundle?.getDouble("tilt") ?: 0.0, - paddings = MapPaddingsImpl.Zeros, + }, + restore = { storedData -> + val cameraBundle = storedData.getBundle("camera") + val mapDesignBundle = storedData.getBundle("mapDesign") + + HereMapViewState( + stateId = storedData.getString("stateId")!!, + mapDesignType = + HereMapDesign.CreateById( + id = mapDesignBundle?.getInt("id") ?: HereMapDesign.NormalDay.id.value, + ), + initCameraPosition = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = cameraBundle?.getDouble("latitude") ?: 0.0, + longitude = cameraBundle?.getDouble("longitude") ?: 0.0, + ), + zoom = cameraBundle?.getDouble("zoom") ?: 0.0, + bearing = cameraBundle?.getDouble("bearing") ?: 0.0, + tilt = cameraBundle?.getDouble("tilt") ?: 0.0, + paddings = MapPaddingsImpl.Zeros, + ), ) - ) - } - -) + }, + ) @Composable fun rememberHereMapViewState( @@ -161,18 +167,22 @@ fun rememberHereMapViewState( val uuid = UUID.randomUUID().toString() mutableStateOf(uuid) } - val state = rememberSaveable( - stateSaver = HereMapViewStateSaver, - ) { - mutableStateOf(HereMapViewState( - stateId = stateId, - mapDesignType = mapDesign, - initCameraPosition = MapCameraPosition.from(cameraPosition), - )) - } + val state = + rememberSaveable( + stateSaver = HereMapViewStateSaver, + ) { + mutableStateOf( + HereMapViewState( + stateId = stateId, + mapDesignType = mapDesign, + initCameraPosition = MapCameraPosition.from(cameraPosition), + ), + ) + } return state.value } + internal fun Context.findActivity(): Activity? = when (this) { is Activity -> this diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/IHereMapEventHandler.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/IHereMapEventHandler.kt index 9c23a8a4..231eae01 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/IHereMapEventHandler.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/IHereMapEventHandler.kt @@ -5,6 +5,8 @@ import com.mapconductor.core.marker.MarkerState interface IHereMapEventHandler { fun onCameraMove(cameraState: MapCamera.State) + fun onMarkerRemove(id: String) + fun onMarkerAdd(state: MarkerState) -} \ No newline at end of file +} diff --git a/mapconductor-for-here/src/main/java/com/mapconductor/here/MapCameraPosition.kt b/mapconductor-for-here/src/main/java/com/mapconductor/here/MapCameraPosition.kt index 2dc2211b..4991363e 100644 --- a/mapconductor-for-here/src/main/java/com/mapconductor/here/MapCameraPosition.kt +++ b/mapconductor-for-here/src/main/java/com/mapconductor/here/MapCameraPosition.kt @@ -6,64 +6,72 @@ import com.here.sdk.mapview.MapCamera import com.here.sdk.mapview.MapCameraUpdate import com.here.sdk.mapview.MapCameraUpdateFactory import com.here.sdk.mapview.MapMeasure -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapCameraPositionBase -import com.mapconductor.core.MapPaddings -import com.mapconductor.core.MapPaddingsImpl import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.map.IMapCameraPosition +import com.mapconductor.core.map.MapCameraPositionBase +import com.mapconductor.core.map.MapPaddings +import com.mapconductor.core.map.MapPaddingsImpl -interface MapCameraPositionHere: IMapCameraPosition { +interface MapCameraPositionHere : IMapCameraPosition { fun toMapCameraUpdate(): MapCameraUpdate + fun toCameraState(): MapCamera.State } @Keep -data class MapCameraPosition @JvmOverloads constructor( - override val position: IGeoPoint, - override val zoom: Double = 2.0, - override val bearing: Double = 0.0, - override val tilt: Double = 0.0, - override val paddings: MapPaddings? = MapPaddingsImpl.Zeros, -): MapCameraPositionBase(position, zoom, bearing, tilt, paddings), MapCameraPositionHere { - - override fun toMapCameraUpdate() = MapCameraUpdateFactory.lookAt( - GeoPoint.from(position).toGeoCoordinates().toUpdate(), - GeoOrientation(bearing, tilt).toUpdate(), - MapMeasure(MapMeasure.Kind.ZOOM_LEVEL, zoom), - ) +data class MapCameraPosition + @JvmOverloads + constructor( + override val position: IGeoPoint, + override val zoom: Double = 2.0, + override val bearing: Double = 0.0, + override val tilt: Double = 0.0, + override val paddings: MapPaddings? = MapPaddingsImpl.Zeros, + ) : MapCameraPositionBase(position, zoom, bearing, tilt, paddings), + MapCameraPositionHere { + override fun toMapCameraUpdate() = + MapCameraUpdateFactory.lookAt( + GeoPoint.from(position).toGeoCoordinates().toUpdate(), + GeoOrientation(bearing, tilt).toUpdate(), + MapMeasure(MapMeasure.Kind.ZOOM_LEVEL, zoom), + ) - override fun toCameraState() = MapCamera.State( - GeoPoint.from(position).toGeoCoordinates(), - GeoOrientation(bearing, tilt), - 0.0, - zoom, - ) + override fun toCameraState() = + MapCamera.State( + GeoPoint.from(position).toGeoCoordinates(), + GeoOrientation(bearing, tilt), + 0.0, + zoom, + ) - companion object { - val Default = MapCameraPosition( - position = GeoPoint.fromLatLong( - latitude = 0.0, - longitude = 0.0, - ), - zoom = 0.0, - bearing = 0.0, - tilt = 0.0, - ) - - fun from(position: IMapCameraPosition) = - when(position) { - is MapCameraPosition -> position - else -> MapCameraPosition( - position = GeoPoint.from(position.position), - zoom = position.zoom, - bearing = position.bearing, - tilt = position.tilt, - paddings = position.paddings, + companion object { + val Default = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = 0.0, + longitude = 0.0, + ), + zoom = 0.0, + bearing = 0.0, + tilt = 0.0, ) - } + + fun from(position: IMapCameraPosition) = + when (position) { + is MapCameraPosition -> position + else -> + MapCameraPosition( + position = GeoPoint.from(position.position), + zoom = position.zoom, + bearing = position.bearing, + tilt = position.tilt, + paddings = position.paddings, + ) + } + } } -} fun MapCamera.State.toMapCameraPosition() = MapCameraPosition( diff --git a/mapconductor-for-here/src/test/java/com/mapconductor/here/ExampleUnitTest.kt b/mapconductor-for-here/src/test/java/com/mapconductor/here/ExampleUnitTest.kt index b1d511d2..8c5d66f8 100644 --- a/mapconductor-for-here/src/test/java/com/mapconductor/here/ExampleUnitTest.kt +++ b/mapconductor-for-here/src/test/java/com/mapconductor/here/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.mapconductor.here +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/mapconductor-for-mapbox/build.gradle.kts b/mapconductor-for-mapbox/build.gradle.kts index eb03823d..bb431e33 100644 --- a/mapconductor-for-mapbox/build.gradle.kts +++ b/mapconductor-for-mapbox/build.gradle.kts @@ -3,6 +3,15 @@ plugins { id("org.jetbrains.kotlin.android") id("maven-publish") alias(libs.plugins.kotlin.compose) // ← 任意(配布したい場合) + id("org.jlleitschuh.gradle.ktlint") +} + +ktlint { + android.set(true) + reporters { + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.PLAIN) + reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE) + } } android { @@ -28,7 +37,7 @@ android { isMinifyEnabled = project.property("isMinifyEnabled").toString().toBoolean() proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" + "proguard-rules.pro", ) } } @@ -58,4 +67,4 @@ dependencies { testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) -} \ No newline at end of file +} diff --git a/mapconductor-for-mapbox/src/androidTest/java/com/mapconductor/mapbox/ExampleInstrumentedTest.kt b/mapconductor-for-mapbox/src/androidTest/java/com/mapconductor/mapbox/ExampleInstrumentedTest.kt index 00472dbd..dc43fd6b 100644 --- a/mapconductor-for-mapbox/src/androidTest/java/com/mapconductor/mapbox/ExampleInstrumentedTest.kt +++ b/mapconductor-for-mapbox/src/androidTest/java/com/mapconductor/mapbox/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.mapconductor.mapbox -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * @@ -21,4 +19,4 @@ class ExampleInstrumentedTest { val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("com.mapconductor.googlemaps", appContext.packageName) } -} \ No newline at end of file +} diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/GeoPoint.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/GeoPoint.kt index f627ffbe..bd3be183 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/GeoPoint.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/GeoPoint.kt @@ -3,6 +3,8 @@ package com.mapconductor.mapbox import com.mapbox.geojson.Point import com.mapconductor.core.features.GeoPoint -fun GeoPoint.toPoint() : Point = Point.fromLngLat(longitude, latitude) +fun GeoPoint.toPoint(): Point = Point.fromLngLat(longitude, latitude) + fun GeoPoint.Companion.from(point: Point) = GeoPoint(point.latitude(), point.longitude()) + fun Point.toGeoPoint() = GeoPoint.fromLongLat(longitude(), latitude()) diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/IMapboxMapEventHandler.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/IMapboxMapEventHandler.kt index a2e36d6e..2db62a44 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/IMapboxMapEventHandler.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/IMapboxMapEventHandler.kt @@ -5,6 +5,8 @@ import com.mapconductor.core.marker.MarkerState internal interface IMapboxMapEventHandler { fun onCameraMove(cameraState: CameraState) + fun onMarkerAdd(state: MarkerState) + fun onMarkerRemove(id: String) -} \ No newline at end of file +} diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapCameraPosition.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapCameraPosition.kt index a889435c..10dcc3ec 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapCameraPosition.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapCameraPosition.kt @@ -4,13 +4,14 @@ import androidx.annotation.Keep import com.mapbox.maps.CameraOptions import com.mapbox.maps.CameraState import com.mapbox.maps.EdgeInsets -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapCameraPositionBase import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.map.IMapCameraPosition +import com.mapconductor.core.map.MapCameraPositionBase -interface MapCameraPositionMBox: IMapCameraPosition { +interface MapCameraPositionMBox : IMapCameraPosition { fun toCameraOptions(): CameraOptions + fun toCameraState(): CameraState // fun copy( @@ -29,48 +30,55 @@ data class MapCameraPosition( override val bearing: Double = 0.0, override val tilt: Double = 0.0, override val paddings: IMapboxPaddings? = MapboxPaddings.Zeros, -): MapCameraPositionBase(position, zoom, bearing, tilt, paddings), MapCameraPositionMBox { - - override fun toCameraOptions() = CameraOptions.Builder() - .center(GeoPoint.from(position).toPoint()) - .zoom(zoom) - .pitch(tilt) - .bearing(bearing) - .padding(paddings?.toEdgeInsects()) - .build() +) : MapCameraPositionBase(position, zoom, bearing, tilt, paddings), + MapCameraPositionMBox { + override fun toCameraOptions() = + CameraOptions + .Builder() + .center(GeoPoint.from(position).toPoint()) + .zoom(zoom) + .pitch(tilt) + .bearing(bearing) + .padding(paddings?.toEdgeInsects()) + .build() - override fun toCameraState() = CameraState( - GeoPoint.from(position).toPoint(), - EdgeInsets(0.0, 0.0, 0.0, 0.0), - zoom, - bearing, - tilt, - ) + override fun toCameraState() = + CameraState( + GeoPoint.from(position).toPoint(), + EdgeInsets(0.0, 0.0, 0.0, 0.0), + zoom, + bearing, + tilt, + ) companion object { - val Default = MapCameraPosition( - position = GeoPoint.fromLatLong( - latitude = 0.0, - longitude = 0.0, - ), - zoom = 0.0, - bearing = 0.0, - tilt = 0.0, - ) + val Default = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = 0.0, + longitude = 0.0, + ), + zoom = 0.0, + bearing = 0.0, + tilt = 0.0, + ) fun from(cameraPosition: IMapCameraPosition) = - when(cameraPosition) { + when (cameraPosition) { is MapCameraPosition -> cameraPosition - else -> MapCameraPosition( - position = GeoPoint.from(cameraPosition.position), - zoom = cameraPosition.zoom, - bearing = cameraPosition.bearing, - tilt = cameraPosition.tilt, - paddings = MapboxPaddings.fromImpl(cameraPosition.paddings), - ) + else -> + MapCameraPosition( + position = GeoPoint.from(cameraPosition.position), + zoom = cameraPosition.zoom, + bearing = cameraPosition.bearing, + tilt = cameraPosition.tilt, + paddings = MapboxPaddings.fromImpl(cameraPosition.paddings), + ) } } } + fun CameraOptions.toMapCameraPosition() = MapCameraPosition( position = center?.toGeoPoint() ?: GeoPoint.fromLongLat(0.0, 0.0), diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxExtension.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxExtension.kt index f497b063..d0b9c7fa 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxExtension.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxExtension.kt @@ -13,8 +13,8 @@ internal fun BitmapIcon.toPointAnnotationOptions(): PointAnnotationOptions { val anchorPixelY = iconH * this.anchor.y // IconAnchor.BOTTOM は「画像下端の中央」が基準なので、そこからの差分を求める - val baseX = iconW / 2.0 // center - val baseY = iconH // bottom + val baseX = iconW / 2.0 // center + val baseY = iconH // bottom val offsetX = anchorPixelX - baseX val offsetY = anchorPixelY - baseY @@ -24,4 +24,3 @@ internal fun BitmapIcon.toPointAnnotationOptions(): PointAnnotationOptions { .withIconAnchor(IconAnchor.BOTTOM) .withIconOffset(listOf(offsetX, offsetY)) } - diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt index 0a27ea85..e802769c 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapDesign.kt @@ -1,42 +1,54 @@ package com.mapconductor.mapbox -import com.mapconductor.core.MapDesignType +import com.mapconductor.core.map.MapDesignType typealias MapboxDesignType = MapDesignType sealed class MapboxMapDesign( override val id: String, -): MapboxDesignType { +) : MapboxDesignType { object Standard : MapboxMapDesign("standard") + object StandardSatellite : MapboxMapDesign("standard-satellite") + object Streets : MapboxMapDesign("streets-v12") + object Outdoors : MapboxMapDesign("outdoors-v12") + object Light : MapboxMapDesign("light-v11") + object Dark : MapboxMapDesign("dark-v11") + object Satellite : MapboxMapDesign("satellite-v9") + object SatelliteStreets : MapboxMapDesign("satellite-streets-v12") + object NavigationDay : MapboxMapDesign("navigation-day-v1") + object NavigationNight : MapboxMapDesign("navigation-night-v1") - class Custom(layerId: String) : MapboxMapDesign(layerId) + class Custom( + layerId: String, + ) : MapboxMapDesign(layerId) - override fun getValue(): String= "${MAPBOX_URL}/${this.id}" + override fun getValue(): String = "${MAPBOX_URL}/${this.id}" companion object { val MAPBOX_URL = "mapbox://styles/mapbox" - fun Create(layerId: String): MapboxMapDesign = when(layerId) { - Standard.id -> Standard - StandardSatellite.id -> StandardSatellite - Streets.id -> Streets - Outdoors.id -> Outdoors - Light.id -> Light - Dark.id -> Dark - Satellite.id -> Satellite - SatelliteStreets.id -> SatelliteStreets - NavigationDay.id -> NavigationDay - NavigationNight.id -> NavigationNight - else -> Custom(layerId) - } + fun Create(layerId: String): MapboxMapDesign = + when (layerId) { + Standard.id -> Standard + StandardSatellite.id -> StandardSatellite + Streets.id -> Streets + Outdoors.id -> Outdoors + Light.id -> Light + Dark.id -> Dark + Satellite.id -> Satellite + SatelliteStreets.id -> SatelliteStreets + NavigationDay.id -> NavigationDay + NavigationNight.id -> NavigationNight + else -> Custom(layerId) + } } -} \ No newline at end of file +} diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt index 22e9d8d9..e46d2754 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapView.kt @@ -1,8 +1,5 @@ package com.mapconductor.mapbox -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -12,6 +9,9 @@ import com.mapbox.maps.CameraOptions import com.mapbox.maps.MapInitOptions import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.map.MapViewBase +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper @Composable fun MapboxMapView( @@ -35,22 +35,26 @@ fun MapboxMapView( scope = scope, registry = registry, onInitialize = { - val cameraOptions = state.mapCameraPosition.value?.let { it -> - CameraOptions.Builder().apply { - center(GeoPoint.from(it.position).toPoint()) - zoom(it.zoom) - pitch(it.tilt) - bearing(it.bearing) - }.build() - } + val cameraOptions = + state.mapCameraPosition.value?.let { it -> + CameraOptions + .Builder() + .apply { + center(GeoPoint.from(it.position).toPoint()) + zoom(it.zoom) + pitch(it.tilt) + bearing(it.bearing) + }.build() + } val styleUri = state.mapDesignType.getValue() - val mapInitOptions = MapInitOptions( - context = context, - textureView = true, - styleUri = styleUri, - cameraOptions = cameraOptions, - ) + val mapInitOptions = + MapInitOptions( + context = context, + textureView = true, + styleUri = styleUri, + cameraOptions = cameraOptions, + ) val holder = MapboxMapViewHolderImpl.create(context, mapInitOptions) // val holder = MapboxMapViewHolderStore.getOrCreate( @@ -59,10 +63,11 @@ fun MapboxMapView( // options = mapInitOptions, // ) val eventHandler = state as? IMapboxMapEventHandler - val controller = MapboxMapViewController( - holder = holder, - eventHandler = eventHandler, - ) + val controller = + MapboxMapViewController( + holder = holder, + eventHandler = eventHandler, + ) (state as? MapboxMapViewState)?.controller = controller holderRef.value = holder @@ -72,7 +77,7 @@ fun MapboxMapView( // Pass content if it needs to be rendered within the overlay providers in MapViewBase, // or handle it here if it's specific to GoogleMapsView structure before calling MapViewBase. // For now, assuming content relates to overlay definitions. - content = content // This might need adjustment based on how overlays are handled + content = content, // This might need adjustment based on how overlays are handled ) } @@ -81,4 +86,4 @@ internal fun Context.findActivity(): Activity? = is Activity -> this is ContextWrapper -> baseContext.findActivity() else -> null - } \ No newline at end of file + } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt index 51338a22..aff0e0a0 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewController.kt @@ -1,7 +1,5 @@ package com.mapconductor.mapbox -import android.animation.Animator -import android.util.AttributeSet import com.google.gson.JsonObject import com.mapbox.maps.CameraChanged import com.mapbox.maps.CameraChangedCallback @@ -26,6 +24,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.lang.ref.WeakReference +import android.animation.Animator +import android.util.AttributeSet interface IMapboxMapInitOptions { val mapOptions: MapOptions? @@ -36,6 +36,7 @@ interface IMapboxMapInitOptions { val attrs: AttributeSet? val antialiasingSampleCount: Int? } + data class MapboxMapInitOptions( override val mapOptions: MapOptions? = null, override val plugins: List? = null, @@ -46,20 +47,26 @@ data class MapboxMapInitOptions( override val antialiasingSampleCount: Int? = null, ) : IMapboxMapInitOptions -interface IMapboxMapViewController: MapViewController { - fun moveCamera(dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback? = null) - fun animateCamera(dstPosition: MapCameraPosition, duration: Long, listener: MapViewState.MoveCameraCallback? = null) +interface IMapboxMapViewController : MapViewController { + fun moveCamera( + dstPosition: MapCameraPosition, + listener: MapViewState.MoveCameraCallback? = null, + ) + + fun animateCamera( + dstPosition: MapCameraPosition, + duration: Long, + listener: MapViewState.MoveCameraCallback? = null, + ) } internal class MapboxMapViewController( override val holder: MapboxMapViewHolder, eventHandler: IMapboxMapEventHandler?, override val coroutine: CoroutineScope = CoroutineScope(Dispatchers.Default), -): IMapboxMapViewController, +) : IMapboxMapViewController, CameraChangedCallback, - OnPointAnnotationClickListener -{ - + OnPointAnnotationClickListener { private lateinit var pointAnnotationManager: PointAnnotationManager init { @@ -75,53 +82,58 @@ internal class MapboxMapViewController( holder.map.subscribeCameraChanged(this) } - private val baseMapViewController = BaseMapViewController( - coroutine = coroutine, - onMarkerRemove = { id, marker -> - synchronized(pointAnnotationManager) { - pointAnnotationManager.delete(marker) - } - }, - onMarkerAdd = { newMarkers -> - synchronized(pointAnnotationManager) { - val options = newMarkers.map { params -> - return@map params.icon.toPointAnnotationOptions() - .withPoint( - GeoPoint.from(params.entry.state.position).toPoint(), - ) - .withData(JsonObject().apply { - addProperty("id", params.entry.id) - }) + private val baseMapViewController = + BaseMapViewController( + coroutine = coroutine, + onMarkerRemove = { id, marker -> + synchronized(pointAnnotationManager) { + pointAnnotationManager.delete(marker) } - return@BaseMapViewController pointAnnotationManager.create(options) - } - - }, - onMarkerChanged = { changes -> - changes.forEach { params -> - // TODO: アイコンに変更があったかどうかを比較 - val option = params.icon.toPointAnnotationOptions() - .withPoint( - GeoPoint.from(params.entry.state.position).toPoint(), - ) - params.marker.point = GeoPoint.from(params.entry.state.position).toPoint() - params.marker.iconSize = option.iconSize - params.marker.iconImage = option.iconImage - params.marker.iconAnchor = option.iconAnchor - params.marker.iconOffset = option.iconOffset - } - }, - ) + }, + onMarkerAdd = { newMarkers -> + synchronized(pointAnnotationManager) { + val options = + newMarkers.map { params -> + return@map params.icon + .toPointAnnotationOptions() + .withPoint( + GeoPoint.from(params.entry.state.position).toPoint(), + ).withData( + JsonObject().apply { + addProperty("id", params.entry.id) + }, + ) + } + return@BaseMapViewController pointAnnotationManager.create(options) + } + }, + onMarkerChanged = { changes -> + changes.forEach { params -> + // TODO: アイコンに変更があったかどうかを比較 + val option = + params.icon + .toPointAnnotationOptions() + .withPoint( + GeoPoint.from(params.entry.state.position).toPoint(), + ) + params.marker.point = GeoPoint.from(params.entry.state.position).toPoint() + params.marker.iconSize = option.iconSize + params.marker.iconImage = option.iconImage + params.marker.iconAnchor = option.iconAnchor + params.marker.iconOffset = option.iconOffset + } + }, + ) - override suspend fun addMarkers(markerList: List) = - baseMapViewController.addMarkers(markerList) + override suspend fun addMarkers(markerList: List) = baseMapViewController.addMarkers(markerList) override suspend fun clearOverlays() = baseMapViewController.clearOverlays() override fun toScreenOffset(position: IGeoPoint): Offset? { - val pixel = holder.map.pixelForCoordinate( - coordinate = GeoPoint.from(position).toPoint(), - ) + val pixel = + holder.map.pixelForCoordinate( + coordinate = GeoPoint.from(position).toPoint(), + ) return Offset( x = pixel.x, y = pixel.y, @@ -132,7 +144,10 @@ internal class MapboxMapViewController( eventHandlerRef.get()?.onCameraMove(cameraChanged.cameraState) } - override fun moveCamera(dstPosition: MapCameraPosition, listener: MapViewState.MoveCameraCallback?) { + override fun moveCamera( + dstPosition: MapCameraPosition, + listener: MapViewState.MoveCameraCallback?, + ) { coroutine.launch { holder.map.setCamera(dstPosition.toCameraOptions()) } @@ -142,34 +157,37 @@ internal class MapboxMapViewController( override fun animateCamera( dstPosition: MapCameraPosition, durationMs: Long, - listener: MapViewState.MoveCameraCallback? + listener: MapViewState.MoveCameraCallback?, ) { - val targetCamera = dstPosition.toCameraOptions() - val animationOptions = MapAnimationOptions.Builder() - .duration(durationMs) - .build() - - val animatorListener = object : Animator.AnimatorListener { - override fun onAnimationStart(animation: Animator) { - // Do nothing here - } + val targetCamera = dstPosition.toCameraOptions() + val animationOptions = + MapAnimationOptions + .Builder() + .duration(durationMs) + .build() + + val animatorListener = + object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) { + // Do nothing here + } - override fun onAnimationEnd(animation: Animator) { - listener?.onComplete(true) - } + override fun onAnimationEnd(animation: Animator) { + listener?.onComplete(true) + } - override fun onAnimationCancel(animation: Animator) { - listener?.onComplete(false) - } + override fun onAnimationCancel(animation: Animator) { + listener?.onComplete(false) + } - override fun onAnimationRepeat(animation: Animator) { - // Do nothing here + override fun onAnimationRepeat(animation: Animator) { + // Do nothing here + } } - } coroutine.launch { holder.map.flyTo( - cameraOptions = targetCamera, + cameraOptions = targetCamera, animationOptions = animationOptions, animatorListener = animatorListener, ) @@ -188,5 +206,4 @@ internal class MapboxMapViewController( } return true } - -} \ No newline at end of file +} diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderImpl.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderImpl.kt index 0387e5e3..ab80ef84 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderImpl.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderImpl.kt @@ -1,16 +1,17 @@ package com.mapconductor.mapbox -import android.content.Context import com.mapbox.maps.MapInitOptions import com.mapbox.maps.MapView import com.mapbox.maps.MapboxLifecycleObserver import com.mapbox.maps.MapboxMap import com.mapbox.maps.plugin.lifecycle.lifecycle -import com.mapconductor.core.MapViewHolder +import com.mapconductor.core.map.MapViewHolder +import android.content.Context class MapboxMapViewHolderImpl private constructor( - override val mapView: MapView -): MapViewHolder, MapboxLifecycleObserver { + override val mapView: MapView, +) : MapViewHolder, + MapboxLifecycleObserver { override lateinit var map: MapboxMap init { @@ -22,7 +23,7 @@ class MapboxMapViewHolderImpl private constructor( context: Context, mapInitOptions: MapInitOptions, ): MapViewHolder { - val mapView = MapView(context, mapInitOptions) + val mapView = MapView(context, mapInitOptions) val holder = MapboxMapViewHolderImpl(mapView) holder.map = mapView.mapboxMap return holder @@ -42,7 +43,10 @@ class MapboxMapViewHolderImpl private constructor( // // override fun destroy() = Unit override fun onDestroy() = Unit + override fun onLowMemory() = Unit + override fun onStart() = Unit + override fun onStop() = Unit } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderStore.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderStore.kt index aee49126..2e22579a 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderStore.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxMapViewHolderStore.kt @@ -1,20 +1,19 @@ package com.mapconductor.mapbox -import android.annotation.SuppressLint -import android.content.Context import com.mapbox.maps.MapInitOptions import com.mapbox.maps.MapView import com.mapbox.maps.MapboxMap -import com.mapconductor.core.MapViewHolder -import com.mapconductor.core.MapViewHolderStoreBaseAsync -import com.mapconductor.core.StaticHolder +import com.mapconductor.core.map.MapViewHolder +import com.mapconductor.core.map.MapViewHolderStoreBaseAsync +import com.mapconductor.core.map.StaticHolder +import android.annotation.SuppressLint +import android.content.Context typealias MapboxMapViewHolder = MapViewHolder object MapboxMapViewOptionsStore : StaticHolder() object MapboxMapViewHolderStore : MapViewHolderStoreBaseAsync() { - @SuppressLint("RestrictedApi") override suspend fun getOrCreate( context: Context, @@ -29,10 +28,11 @@ object MapboxMapViewHolderStore : MapViewHolderStoreBaseAsync paddings - else -> MapboxPaddings( - top = paddings.top, - left = paddings.left, - bottom = paddings.bottom, - right = paddings.right, - ) + else -> + MapboxPaddings( + top = paddings.top, + left = paddings.left, + bottom = paddings.bottom, + right = paddings.right, + ) } } } diff --git a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt index e69dc06e..ee5d9928 100644 --- a/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt +++ b/mapconductor-for-mapbox/src/main/java/com/mapconductor/mapbox/MapboxViewState.kt @@ -1,17 +1,16 @@ package com.mapconductor.mapbox -import android.os.Bundle import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import com.mapbox.maps.CameraState -import com.mapconductor.core.IMapCameraPosition -import com.mapconductor.core.MapCameraPositionBase import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.features.IGeoPoint +import com.mapconductor.core.map.IMapCameraPosition import com.mapconductor.core.map.InitState +import com.mapconductor.core.map.MapCameraPositionBase import com.mapconductor.core.map.MapViewState import com.mapconductor.core.map.MapViewStateImpl import com.mapconductor.core.marker.MarkerState @@ -22,22 +21,23 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import java.util.UUID +import android.os.Bundle -interface IMapboxMapViewState: MapViewState +interface IMapboxMapViewState : MapViewState class MapboxMapViewState( override val stateId: String, override val mapDesignType: MapboxDesignType, override val initCameraPosition: MapCameraPosition = MapCameraPosition.Default, -) : MapViewStateImpl(), IMapboxMapViewState, IMapboxMapEventHandler { - +) : MapViewStateImpl(), + IMapboxMapViewState, + IMapboxMapEventHandler { internal var controller: IMapboxMapViewController? = null // Camera center position - private val _cameraState = MutableStateFlow(initCameraPosition.toCameraState()) -// private val cameraState: StateFlow = _cameraState.asStateFlow() + private val cameraState = MutableStateFlow(initCameraPosition.toCameraState()) override val mapCameraPosition: StateFlow = - _cameraState.map { it.toMapCameraPosition() }.stateIn( + cameraState.map { it.toMapCameraPosition() }.stateIn( scope = this.mainCoroutine, started = SharingStarted.Eagerly, initialValue = null, @@ -46,7 +46,7 @@ class MapboxMapViewState( override fun moveCameraTo( position: IGeoPoint, durationMs: Long, - listener: MapViewState.MoveCameraCallback? + listener: MapViewState.MoveCameraCallback?, ) { if (this.isInitialized.value != InitState.Initialized) { this.warningLog("moveCameraTo() called before map is initialized.") @@ -58,16 +58,17 @@ class MapboxMapViewState( listener?.onComplete(false) return } - val newPosition = currCameraPosition.copy( - position = GeoPoint.from(position), - ) + val newPosition = + currCameraPosition.copy( + position = GeoPoint.from(position), + ) this.moveCameraTo(newPosition, durationMs, listener) } override fun moveCameraTo( position: IMapCameraPosition, durationMs: Long, - listener: MapViewState.MoveCameraCallback? + listener: MapViewState.MoveCameraCallback?, ) { if (this.isInitialized.value != InitState.Initialized) { this.warningLog("moveCameraTo() called before map is initialized.") @@ -86,9 +87,9 @@ class MapboxMapViewState( } } - override fun onCameraMove(cameraState: CameraState) { - _cameraState.value = cameraState - this.debugLog("--->camera = ${cameraState.center.toGeoPoint().toUrlValue()}") + override fun onCameraMove(state: CameraState) { + cameraState.value = state + this.debugLog("--->camera = ${state.center.toGeoPoint().toUrlValue()}") } override fun onMarkerAdd(state: MarkerState) { @@ -100,51 +101,56 @@ class MapboxMapViewState( } } -val MapboxMapViewStateSaver = Saver( - save = { state -> - val cameraStateBundle = state.mapCameraPosition.value?.let { cameraState -> +val MapboxMapViewStateSaver = + Saver( + save = { state -> + val cameraStateBundle = + state.mapCameraPosition.value?.let { cameraState -> + Bundle().apply { + putDouble("zoom", cameraState.zoom) + putDouble("tilt", cameraState.tilt) + putDouble("bearing", cameraState.bearing) + putDouble("latitude", cameraState.position.latitude) + putDouble("longitude", cameraState.position.longitude) + } + } + + val mapDesignBundle = + Bundle().apply { + putString("id", state.mapDesignType.id) + } + Bundle().apply { - putDouble("zoom", cameraState.zoom) - putDouble("tilt", cameraState.tilt) - putDouble("bearing", cameraState.bearing) - putDouble("latitude", cameraState.position.latitude) - putDouble("longitude", cameraState.position.longitude) + putString("stateId", state.stateId) + putBundle("mapDesign", mapDesignBundle) + putBundle("camera", cameraStateBundle) } - } - - val mapDesignBundle = Bundle().apply { - putString("id", state.mapDesignType.id) - } - - Bundle().apply { - putString("stateId", state.stateId) - putBundle("mapDesign", mapDesignBundle) - putBundle("camera", cameraStateBundle) - } - }, - restore = { storedData -> - val cameraBundle = storedData.getBundle("camera") - val mapDesignBundle = storedData.getBundle("mapDesign") - - MapboxMapViewState( - stateId = storedData.getString("stateId")!!, - mapDesignType = MapboxMapDesign.Create( - layerId = mapDesignBundle?.getString("id") ?: Standard.id, - ), - initCameraPosition = MapCameraPosition( - position = GeoPoint.fromLatLong( - latitude = cameraBundle?.getDouble("latitude") ?: 0.0, - longitude = cameraBundle?.getDouble("longitude") ?: 0.0, - ), - zoom = cameraBundle?.getDouble("zoom") ?: 0.0, - bearing = cameraBundle?.getDouble("bearing") ?: 0.0, - tilt = cameraBundle?.getDouble("tilt") ?: 0.0, - paddings = null + }, + restore = { storedData -> + val cameraBundle = storedData.getBundle("camera") + val mapDesignBundle = storedData.getBundle("mapDesign") + + MapboxMapViewState( + stateId = storedData.getString("stateId")!!, + mapDesignType = + MapboxMapDesign.Create( + layerId = mapDesignBundle?.getString("id") ?: Standard.id, + ), + initCameraPosition = + MapCameraPosition( + position = + GeoPoint.fromLatLong( + latitude = cameraBundle?.getDouble("latitude") ?: 0.0, + longitude = cameraBundle?.getDouble("longitude") ?: 0.0, + ), + zoom = cameraBundle?.getDouble("zoom") ?: 0.0, + bearing = cameraBundle?.getDouble("bearing") ?: 0.0, + tilt = cameraBundle?.getDouble("tilt") ?: 0.0, + paddings = null, + ), ) - ) - - } -) + }, + ) @Composable fun rememberMapboxMapViewState( @@ -155,13 +161,16 @@ fun rememberMapboxMapViewState( val uuid = UUID.randomUUID().toString() mutableStateOf(uuid) } - val state = rememberSaveable(stateSaver = MapboxMapViewStateSaver) { - mutableStateOf(MapboxMapViewState( - stateId = stateId, - mapDesignType = mapDesign, - initCameraPosition = MapCameraPosition.from(cameraPosition), - )) - } + val state = + rememberSaveable(stateSaver = MapboxMapViewStateSaver) { + mutableStateOf( + MapboxMapViewState( + stateId = stateId, + mapDesignType = mapDesign, + initCameraPosition = MapCameraPosition.from(cameraPosition), + ), + ) + } return state.value } diff --git a/mapconductor-for-mapbox/src/test/java/com/mapconductor/mapbox/ExampleUnitTest.kt b/mapconductor-for-mapbox/src/test/java/com/mapconductor/mapbox/ExampleUnitTest.kt index a6cfbb0e..1a11d918 100644 --- a/mapconductor-for-mapbox/src/test/java/com/mapconductor/mapbox/ExampleUnitTest.kt +++ b/mapconductor-for-mapbox/src/test/java/com/mapconductor/mapbox/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.mapconductor.mapbox +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * @@ -14,4 +13,4 @@ class ExampleUnitTest { fun addition_isCorrect() { assertEquals(4, 2 + 2) } -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 128a46d2..07bfcb30 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,19 +31,15 @@ dependencyResolutionManagement { } rootProject.name = "MapConductorSDK" -//include( -// ":example-app", -// ":mapconductor-for-here", -// ":mapconductor-for-mapbox", -// ":mapconductor-for-googlemaps", -// ":mapconductor-for-arcgis", -// ":mapconductor-core", -//) -val modulesProp = rootDir.resolve("projects.properties").readLines() - .firstOrNull { it.startsWith("modules=") } - ?.removePrefix("modules=") - ?.split(",") - ?.map { it.trim() } - ?: emptyList() + +val modulesProp = + rootDir + .resolve("projects.properties") + .readLines() + .firstOrNull { it.startsWith("modules=") } + ?.removePrefix("modules=") + ?.split(",") + ?.map { it.trim() } + ?: emptyList() modulesProp.forEach { include(":$it") } From e9cc94c0397ebd8205f6352810f3ebfb3077304a Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 10:23:57 +0900 Subject: [PATCH 14/20] fix: no value for is provided --- local.defaults.properties | 1 + projects.properties | 1 + 2 files changed, 2 insertions(+) diff --git a/local.defaults.properties b/local.defaults.properties index c8155b75..f29a17b2 100644 --- a/local.defaults.properties +++ b/local.defaults.properties @@ -1,4 +1,5 @@ HERE_ACCESS_KEY_ID=HERE_ACCESS_KEY_ID HERE_ACCESS_SECRET=HERE_ACCESS_SECRET +HERE_ACCESS_KEY_SECRET=HERE_ACCESS_KEY_SECRET MAPS_API_KEY=MAPS_API_KEY ARCGIS_API_KEY=ARCGIS_API_KEY diff --git a/projects.properties b/projects.properties index 2caac8a1..89f5d353 100644 --- a/projects.properties +++ b/projects.properties @@ -1 +1,2 @@ +// In order to share the module names with gradle and KtLinter, define module names are below modules=example-app,mapconductor-for-here,mapconductor-for-mapbox,mapconductor-for-googlemaps,mapconductor-for-arcgis,mapconductor-core From 897e2100a9f202ffe04b3b2542ab7e1dd8c99fcb Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 11:04:11 +0900 Subject: [PATCH 15/20] Separate lint and review actions --- .github/workflows/{ci.yml => lint.yml} | 36 +++---- .github/workflows/review.yml | 129 +++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 24 deletions(-) rename .github/workflows/{ci.yml => lint.yml} (69%) create mode 100644 .github/workflows/review.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/lint.yml similarity index 69% rename from .github/workflows/ci.yml rename to .github/workflows/lint.yml index 2c856e5c..5b2f957d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/lint.yml @@ -1,15 +1,15 @@ -name: Android Code Check (Lint + KtLint + ReviewDog) +name: Lint Check on Push on: push: - branches: [ "**" ] - pull_request: - branches: [ "**" ] + branches: # 必要に応じて特定のブランチを除外・指定できます + - '**' # 全てのブランチへのpushで実行 + # - '!main' # 例: mainブランチへの直接pushでは実行しない場合 jobs: - code-check: + lint: runs-on: ubuntu-latest - name: Lint & ReviewDog on Changed Modules + name: Lint Changed Modules steps: - name: Checkout @@ -45,6 +45,7 @@ jobs: if [ -f "$CACHE_KEY_FILE" ]; then old_hash=$(cat "$CACHE_KEY_FILE") if [ "$hash" == "$old_hash" ]; then + echo "Module $module unchanged." continue fi fi @@ -58,6 +59,8 @@ jobs: if: steps.detect.outputs.changed_modules != '' run: | for module in ${{ steps.detect.outputs.changed_modules }}; do + echo "Running ktlintCheck for $module" + # エラーがあってもCIを止めない場合は `|| true` やエラーハンドリングを追加 ./gradlew ":$module:ktlintCheck" done @@ -65,28 +68,13 @@ jobs: if: steps.detect.outputs.changed_modules != '' run: | for module in ${{ steps.detect.outputs.changed_modules }}; do + echo "Running lint for $module" + # エラーがあってもCIを止めない場合は `|| true` やエラーハンドリングを追加 ./gradlew ":$module:lint" done - - name: Run ReviewDog (only on pull_request) - if: github.event_name == 'pull_request' && steps.detect.outputs.changed_modules != '' - run: | - for module in ${{ steps.detect.outputs.changed_modules }}; do - lint_file=$(find "$module" -name "lint-results.xml" | head -n 1) - if [ -f "$lint_file" ]; then - echo "Reporting Lint results for $module" - reviewdog -name="android-lint ($module)" \ - -f=checkstyle \ - -f.diff="git diff origin/${{ github.base_ref }}" \ - -reporter=github-pr-review \ - -level=warning \ - < "$lint_file" - fi - done - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload hash cache + if: always() # 常にキャッシュをアップロード uses: actions/upload-artifact@v4 with: name: lint-hash-cache diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml new file mode 100644 index 00000000..d29b74b6 --- /dev/null +++ b/.github/workflows/review.yml @@ -0,0 +1,129 @@ +name: Code Review with ReviewDog + +on: + pull_request: + branches: [ "**" ] # 全てのターゲットブランチに対するPRで実行 + # types: [opened, synchronize, reopened] # デフォルトでこれらのイベントでトリガー + +jobs: + reviewdog: + runs-on: ubuntu-latest + name: Lint & ReviewDog on Changed Modules + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # ReviewDogが差分を正しく処理するために全履歴を取得 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 17 + + - name: Setup ReviewDog + uses: reviewdog/action-setup@v1 + with: + reviewdog_version: latest # または特定のバージョン + + - name: Download hash cache + uses: actions/download-artifact@v4 + with: + name: lint-hash-cache + path: .lint-hash-cache + continue-on-error: true # 最初の実行やキャッシュがない場合を考慮 + + - name: Read module list from projects.properties + id: define + run: | + modules=$(grep '^modules=' projects.properties | cut -d= -f2 | tr ',' ' ') + echo "modules=$modules" >> $GITHUB_OUTPUT + + - name: Detect changed modules by hashing src/ + id: detect + run: | + changed_modules="" + mkdir -p .lint-hash-cache + for module in ${{ steps.define.outputs.modules }}; do + hash=$(find "$module/src" -type f \( -name "*.kt" -o -name "*.xml" \) | sort | xargs sha256sum | sha256sum | cut -d ' ' -f1) + CACHE_KEY_FILE=".lint-hash-cache/${module//\//-}.txt" + if [ -f "$CACHE_KEY_FILE" ]; then + old_hash=$(cat "$CACHE_KEY_FILE") + if [ "$hash" == "$old_hash" ]; then + echo "Module $module unchanged based on local hash." + # PRの場合、ベースブランチとの差分で変更がなくても、 + # 他の要因でLintが必要な場合もあるため、ここでは単純に続行させるか、 + # PRの差分全体で変更されたファイルを含むモジュールを対象にするロジックも検討できます。 + # シンプルにするため、ここでは元のロジックを維持します。 + # continue # 必要に応じてコメントアウトまたは調整 + fi + fi + echo "Module changed (or needs check for PR): $module" + changed_modules="$changed_modules $module" + echo "$hash" > "$CACHE_KEY_FILE" # PRごとにもハッシュを更新 + done + echo "changed_modules=${changed_modules}" >> $GITHUB_OUTPUT + + - name: Run ktlintCheck + if: steps.detect.outputs.changed_modules != '' + run: | + for module in ${{ steps.detect.outputs.changed_modules }}; do + echo "Running ktlintCheck for $module (for ReviewDog)" + # ReviewDogがレポートを生成するために、エラーがあっても続行する + ./gradlew ":$module:ktlintCheck" || echo "ktlintCheck for $module failed but proceeding to report." + done + + - name: Run Android Lint + if: steps.detect.outputs.changed_modules != '' + run: | + for module in ${{ steps.detect.outputs.changed_modules }}; do + echo "Running lint for $module (for ReviewDog)" + # ReviewDogがレポートを生成するために、エラーがあっても続行する + ./gradlew ":$module:lint" || echo "Lint for $module failed but proceeding to report." + done + + - name: Run ReviewDog for Ktlint + if: steps.detect.outputs.changed_modules != '' # 変更があったモジュールのみ + run: | + for module in ${{ steps.detect.outputs.changed_modules }}; do + ktlint_report_file=$(find "$module/build/reports/ktlint" -name "*.checkstyle.xml" -print -quit) + if [ -n "$ktlint_report_file" ] && [ -f "$ktlint_report_file" ]; then + echo "Reporting Ktlint results for $module using $ktlint_report_file" + reviewdog -name="ktlint ($module)" \ + -f=checkstyle \ + -diff="git diff origin/${{ github.base_ref }}" \ + -reporter=github-pr-review \ + -level=warning < "$ktlint_report_file" + else + echo "Ktlint checkstyle report not found for $module." + fi + done + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run ReviewDog for Android Lint + if: steps.detect.outputs.changed_modules != '' # 変更があったモジュールのみ + run: | + for module in ${{ steps.detect.outputs.changed_modules }}; do + lint_file=$(find "$module/build/reports" -name "lint-results.xml" -print -quit) + if [ -n "$lint_file" ] && [ -f "$lint_file" ]; then + echo "Reporting Android Lint results for $module using $lint_file" + reviewdog -name="android-lint ($module)" \ + -f=checkstyle \ + -diff="git diff origin/${{ github.base_ref }}" \ + -reporter=github-pr-review \ + -level=warning < "$lint_file" + else + echo "Android Lint report (lint-results.xml) not found for $module." + fi + done + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload hash cache + if: always() # 常にキャッシュをアップロード + uses: actions/upload-artifact@v4 + with: + name: lint-hash-cache # lint.yml と同じキャッシュ名を使用 + path: .lint-hash-cache/ From 75c3c7355b244264980d4f17d63d7151b6db571c Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 11:49:31 +0900 Subject: [PATCH 16/20] use claude PR assistant --- .github/workflows/review.yml | 144 +++++++---------------------------- 1 file changed, 29 insertions(+), 115 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index d29b74b6..6b0f630d 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -1,129 +1,43 @@ -name: Code Review with ReviewDog +name: Claude PR Assistant on: - pull_request: - branches: [ "**" ] # 全てのターゲットブランチに対するPRで実行 - # types: [opened, synchronize, reopened] # デフォルトでこれらのイベントでトリガー + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] jobs: - reviewdog: + claude-code-action: + if: | + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || + (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest - name: Lint & ReviewDog on Changed Modules - + permissions: + contents: read + pull-requests: read + issues: read + id-token: write steps: - - name: Checkout + - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 0 # ReviewDogが差分を正しく処理するために全履歴を取得 + fetch-depth: 1 - - name: Set up Java - uses: actions/setup-java@v4 + - uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 - - - name: Setup ReviewDog - uses: reviewdog/action-setup@v1 - with: - reviewdog_version: latest # または特定のバージョン + java-version: '17' - - name: Download hash cache - uses: actions/download-artifact@v4 + - name: Run Claude PR Action + id: claude + uses: anthropics/claude-code-action@beta with: - name: lint-hash-cache - path: .lint-hash-cache - continue-on-error: true # 最初の実行やキャッシュがない場合を考慮 - - - name: Read module list from projects.properties - id: define - run: | - modules=$(grep '^modules=' projects.properties | cut -d= -f2 | tr ',' ' ') - echo "modules=$modules" >> $GITHUB_OUTPUT - - - name: Detect changed modules by hashing src/ - id: detect - run: | - changed_modules="" - mkdir -p .lint-hash-cache - for module in ${{ steps.define.outputs.modules }}; do - hash=$(find "$module/src" -type f \( -name "*.kt" -o -name "*.xml" \) | sort | xargs sha256sum | sha256sum | cut -d ' ' -f1) - CACHE_KEY_FILE=".lint-hash-cache/${module//\//-}.txt" - if [ -f "$CACHE_KEY_FILE" ]; then - old_hash=$(cat "$CACHE_KEY_FILE") - if [ "$hash" == "$old_hash" ]; then - echo "Module $module unchanged based on local hash." - # PRの場合、ベースブランチとの差分で変更がなくても、 - # 他の要因でLintが必要な場合もあるため、ここでは単純に続行させるか、 - # PRの差分全体で変更されたファイルを含むモジュールを対象にするロジックも検討できます。 - # シンプルにするため、ここでは元のロジックを維持します。 - # continue # 必要に応じてコメントアウトまたは調整 - fi - fi - echo "Module changed (or needs check for PR): $module" - changed_modules="$changed_modules $module" - echo "$hash" > "$CACHE_KEY_FILE" # PRごとにもハッシュを更新 - done - echo "changed_modules=${changed_modules}" >> $GITHUB_OUTPUT + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + timeout_minutes: "60" - - name: Run ktlintCheck - if: steps.detect.outputs.changed_modules != '' - run: | - for module in ${{ steps.detect.outputs.changed_modules }}; do - echo "Running ktlintCheck for $module (for ReviewDog)" - # ReviewDogがレポートを生成するために、エラーがあっても続行する - ./gradlew ":$module:ktlintCheck" || echo "ktlintCheck for $module failed but proceeding to report." - done - - - name: Run Android Lint - if: steps.detect.outputs.changed_modules != '' - run: | - for module in ${{ steps.detect.outputs.changed_modules }}; do - echo "Running lint for $module (for ReviewDog)" - # ReviewDogがレポートを生成するために、エラーがあっても続行する - ./gradlew ":$module:lint" || echo "Lint for $module failed but proceeding to report." - done - - - name: Run ReviewDog for Ktlint - if: steps.detect.outputs.changed_modules != '' # 変更があったモジュールのみ - run: | - for module in ${{ steps.detect.outputs.changed_modules }}; do - ktlint_report_file=$(find "$module/build/reports/ktlint" -name "*.checkstyle.xml" -print -quit) - if [ -n "$ktlint_report_file" ] && [ -f "$ktlint_report_file" ]; then - echo "Reporting Ktlint results for $module using $ktlint_report_file" - reviewdog -name="ktlint ($module)" \ - -f=checkstyle \ - -diff="git diff origin/${{ github.base_ref }}" \ - -reporter=github-pr-review \ - -level=warning < "$ktlint_report_file" - else - echo "Ktlint checkstyle report not found for $module." - fi - done - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Run ReviewDog for Android Lint - if: steps.detect.outputs.changed_modules != '' # 変更があったモジュールのみ - run: | - for module in ${{ steps.detect.outputs.changed_modules }}; do - lint_file=$(find "$module/build/reports" -name "lint-results.xml" -print -quit) - if [ -n "$lint_file" ] && [ -f "$lint_file" ]; then - echo "Reporting Android Lint results for $module using $lint_file" - reviewdog -name="android-lint ($module)" \ - -f=checkstyle \ - -diff="git diff origin/${{ github.base_ref }}" \ - -reporter=github-pr-review \ - -level=warning < "$lint_file" - else - echo "Android Lint report (lint-results.xml) not found for $module." - fi - done - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload hash cache - if: always() # 常にキャッシュをアップロード - uses: actions/upload-artifact@v4 - with: - name: lint-hash-cache # lint.yml と同じキャッシュ名を使用 - path: .lint-hash-cache/ From 6eb63f1479afa76b6b4943759f75231d9d3bf78f Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 12:02:17 +0900 Subject: [PATCH 17/20] wip: introducing claude code review --- .github/workflows/review.yml | 41 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index 6b0f630d..e1159f43 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -1,27 +1,15 @@ -name: Claude PR Assistant +name: Claude Auto Review on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] + pull_request: + types: [opened, synchronize] jobs: - claude-code-action: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) + auto-review: runs-on: ubuntu-latest permissions: contents: read pull-requests: read - issues: read id-token: write steps: - name: Checkout repository @@ -29,15 +17,22 @@ jobs: with: fetch-depth: 1 - - uses: actions/setup-java@v4 - with: - distribution: 'temurin' - java-version: '17' - - - name: Run Claude PR Action - id: claude + - name: Automatic PR Review uses: anthropics/claude-code-action@beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} timeout_minutes: "60" + direct_prompt: | + このプルリクエストをレビューして、総合的なレビューを日本語で返してください + + 以下の点にフォーカスしてください + - Code quality and best practices + - Potential bugs or issues + - Performance considerations + - Security implications + - Test coverage + - Documentation updates if needed + Provide constructive feedback with specific suggestions for improvement. + Use inline comments to highlight specific areas of concern. + # allowed_tools: "mcp__github__add_pull_request_review_comment" From 8edd6380317e31b8d4ed5c3a24a1dea8ccc4204c Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 12:15:46 +0900 Subject: [PATCH 18/20] Reduce the propmt length --- .github/workflows/review.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index e1159f43..a6d3dee8 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -28,11 +28,5 @@ jobs: 以下の点にフォーカスしてください - Code quality and best practices - Potential bugs or issues - - Performance considerations - - Security implications - - Test coverage - - Documentation updates if needed - Provide constructive feedback with specific suggestions for improvement. - Use inline comments to highlight specific areas of concern. - # allowed_tools: "mcp__github__add_pull_request_review_comment" + allowed_tools: "mcp__github__add_pull_request_review_comment" From 1fc6c5f90d1576f163f97716b9a695efa5405f3b Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 12:16:24 +0900 Subject: [PATCH 19/20] try to fix: 'Detect changed modules by hashing src/' step does not work correctly --- .github/workflows/lint.yml | 6 ++++-- .github/workflows/review.yml | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5b2f957d..40f568ba 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -50,10 +50,12 @@ jobs: fi fi echo "Module changed: $module" - changed_modules="$changed_modules $module" + changed_modules="${changed_modules:+$changed_modules }$module" echo "$hash" > "$CACHE_KEY_FILE" done - echo "changed_modules=${changed_modules}" >> $GITHUB_OUTPUT + # 余分な空白や改行を除去 + changed_modules=$(echo "$changed_modules" | xargs) + echo "changed_modules=$changed_modules" >> $GITHUB_OUTPUT - name: Run ktlintCheck if: steps.detect.outputs.changed_modules != '' diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml index a6d3dee8..1511d8ff 100644 --- a/.github/workflows/review.yml +++ b/.github/workflows/review.yml @@ -21,11 +21,11 @@ jobs: uses: anthropics/claude-code-action@beta with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - timeout_minutes: "60" + timeout_minutes: "120" direct_prompt: | - このプルリクエストをレビューして、総合的なレビューを日本語で返してください + Please review this pull request and provide comprehensive feedback in Japanese. - 以下の点にフォーカスしてください + Focus on: - Code quality and best practices - Potential bugs or issues From bab1214d4f00d335d5d6a1fa3271e94cd6a366d1 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Wed, 4 Jun 2025 12:22:34 +0900 Subject: [PATCH 20/20] disable claude code review for a while --- .github/workflows/{review.yml => review.yml-disabled} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{review.yml => review.yml-disabled} (100%) diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml-disabled similarity index 100% rename from .github/workflows/review.yml rename to .github/workflows/review.yml-disabled