diff --git a/.classpath b/.classpath
deleted file mode 100644
index 04b10efc..00000000
--- a/.classpath
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml
deleted file mode 100644
index d2fcbee3..00000000
--- a/.github/workflows/ant.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Java CI
-
-on:
- push:
- branches:
- - master
- - $default-branch
- - $protected-branches
- pull_request:
- branches:
- - master
- - $default-branch
- workflow_dispatch:
-
-jobs:
- call-workflow:
- strategy:
- matrix:
- josm-revision: ["", "r19067"]
- uses: JOSM/JOSMPluginAction/.github/workflows/ant.yml@v3
- with:
- java-version: 17
- josm-revision: ${{ matrix.josm-revision }}
- plugin-jar-name: 'mapwithai'
- perform-revision-tagging: ${{ matrix.josm-revision == 'r19067' && github.repository == 'JOSM/MapWithAI' && github.ref_type == 'branch' && github.ref_name == 'master' && github.event_name != 'schedule' && github.event_name != 'pull_request' }}
- secrets: inherit
-
diff --git a/.github/workflows/reports.yaml b/.github/workflows/reports.yaml
deleted file mode 100644
index 007304f0..00000000
--- a/.github/workflows/reports.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-name: Publish reports
-
-on:
- workflow_run:
- workflows: [Java CI]
- types: [completed]
-
-permissions:
- checks: write
-
-jobs:
- call-workflow:
- uses: JOSM/JOSMPluginAction/.github/workflows/reports.yaml@v3
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 34d58eb7..00000000
--- a/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-/.gradle/
-/.idea/
-/bin/
-/build/
-/javadoc/
-/out/
-/checkstyle-josm-rapid.xml
-/findbugs-josm-rapid.xml
-/lib/
-.DS_Store
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f8efcd49..2c90f2ed 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,388 +1,11 @@
-image: registry.gitlab.com/josm/docker-library/openjdk:8
+image: alpine:latest
-cache: &global_cache
- paths:
- - .gradle/wrapper
- - .gradle/caches
-
-before_script:
- - export GRADLE_USER_HOME=`pwd`/.gradle
-
-#############################
-# Various additional Checks #
-#############################
-include:
- - template: Code-Quality.gitlab-ci.yml
- - template: SAST.gitlab-ci.yml
- - template: Dependency-Scanning.gitlab-ci.yml
- - template: License-Scanning.gitlab-ci.yml
-# - template: Container-Scanning.gitlab-ci.yml
-# - template: DAST.gitlab-ci.yml
-
-stages:
- - build
- - test
- - deploy
- - release
-
-sast:
- variables: {}
- variables:
- GRADLE_PATH: "./gradlew"
- FAIL_NEVER: 1
- SAST_EXCLUDED_PATHS: ".gradle"
-# CI_DEBUG_TRACE: "true"
-
-variables:
- GIT_SUBMODULE_STRATEGY: recursive
- PLUGIN_NAME: "MapWithAI"
- PLUGIN_JAR_BASE_NAME: "mapwithai"
-
-###############
-# Build stage #
-###############
-
-assemble:
- stage: build
- script:
- - ./gradlew assemble --stacktrace
- artifacts:
- paths:
- - build/
- expire_in: 1 day
- interruptible: true
-
-assemble with java 11:
- stage: build
- image: registry.gitlab.com/josm/docker-library/openjdk:11
- script:
- - ./gradlew assemble --stacktrace
- artifacts:
- paths:
- - build/
- expire_in: 1 day
- interruptible: true
-
-assemble with java 17:
- stage: build
- image: registry.gitlab.com/josm/docker-library/openjdk:17
- script:
- - ./gradlew assemble --stacktrace
- artifacts:
- paths:
- - build/
- expire_in: 1 day
- allow_failure: true
- interruptible: true
-
-code_navigation:
- stage: build
- script:
- - apk add --update curl bash
- - curl -fLo coursier https://git.io/coursier-cli
- - chmod +x coursier
- - ./coursier launch com.sourcegraph:lsif-java_2.13:0.7.2 -- index --build-tool gradle
- artifacts:
- reports:
- lsif: dump.lsif
- rules:
- - if: '$CI_PIPELINE_SOURCE != "schedule" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- when: always
- allow_failure: true
- interruptible: true
- cache:
- <<: *global_cache
- policy: pull
-
-##############
-# Test stage #
-##############
-
-build:
- stage: test
- script:
- - ./gradlew build localDist --stacktrace #--info
- artifacts:
- paths:
- - build
- expire_in: 1 day
- needs: ["assemble"]
- dependencies:
- - assemble
- interruptible: true
-
-test:
- stage: test
- script:
- - ./gradlew check --stacktrace --continue #--info
- - ./gradlew jacocoTestReport
- - ./gradlew jacocoTestCoverageVerification
- artifacts:
- paths:
- - build
- reports:
- junit: build/test-results/**/TEST-*.xml
- needs: ["assemble"]
- dependencies:
- - assemble
- interruptible: true
- coverage: '/.*Instruction Coverage.*?([0-9.]{1,8}) ?%/'
-
-coverage:
+pages:
stage: deploy
- needs: ["test"]
- dependencies:
- - test
- image: haynes/jacoco2cobertura:1.0.8
- script:
- - python /opt/cover2cover.py build/reports/jacoco/test/jacocoTestReport.xml $CI_PROJECT_DIR/src/main/java/ > build/reports/jacoco/test/coverage.xml
- artifacts:
- reports:
- coverage_report:
- coverage_format: cobertura
- path: "build/reports/jacoco/test/coverage.xml"
- interruptible: true
- cache:
- <<: *global_cache
- policy: pull
-
-translate:
- stage: test
script:
- - ./gradlew generatePot --stacktrace
+ - echo 'Nothing to do...'
artifacts:
paths:
- - build
- needs: ["assemble"]
- cache:
- <<: *global_cache
- policy: pull
-
-compile against min JOSM:
- stage: test
- script:
- - ./gradlew compileJava_minJosm --stacktrace
- needs: ["assemble"]
- dependencies:
- - assemble
- interruptible: true
- allow_failure: true # It should still run against it, but there are some methods if'd around
- cache:
- <<: *global_cache
- policy: pull
-
-compile against latest JOSM:
- stage: test
- script:
- - ./gradlew compileJava_latestJosm --stacktrace
- needs: ["assemble"]
- dependencies:
- - assemble
- interruptible: true
- cache:
- <<: *global_cache
- policy: pull
-
-build with java 11:
- stage: test
- image: registry.gitlab.com/josm/docker-library/openjdk:11
- script:
- - ./gradlew build --stacktrace
- needs: ["assemble with java 11"]
- dependencies:
- - assemble with java 11
- interruptible: true
- cache:
- <<: *global_cache
- policy: pull
-
-build with java 17:
- stage: test
- image: registry.gitlab.com/josm/docker-library/openjdk:17
- script:
- - ./gradlew build --stacktrace
- allow_failure: true
- needs: ["assemble with java 17"]
- dependencies:
- - assemble with java 17
- interruptible: true
- cache:
- <<: *global_cache
- policy: pull
-
-################
-# Deploy stage #
-################
-
-transifex.com:
- image: registry.gitlab.com/josm/docker-library/python-transifex:latest
- stage: deploy
- environment:
- name: transifex.com
- url: https://www.transifex.com/josm/josm/josm-plugin_$PLUGIN_NAME/
- script:
- - TX_TOKEN="$TRANSIFEX_TOKEN" tx push -s --no-interactive
- needs: ["translate"]
- only:
- refs:
- - master
- variables:
- - $TRANSIFEX_TOKEN
- cache:
- <<: *global_cache
- policy: pull
-
-codecov.io:
- image: alpine:3.10
- stage: deploy
- environment:
- name: codecov.io
- url: https://codecov.io/gh/JOSM/$PLUGIN_NAME
- before_script:
- - apk add --update curl bash
- script:
- - curl -s https://codecov.io/bash | bash
- - curl -s https://codecov.io/bash | bash /dev/stdin -c -F model_and_api
- needs: ["build"]
+ - public
only:
- refs:
- - master
- variables:
- - $CODECOV_TOKEN
-
-sonarcloud.io:
- image: registry.gitlab.com/josm/docker-library/openjdk:11
- stage: deploy
- environment:
- name: sonarcloud.io
- url: https://sonarcloud.io/dashboard?id=$PLUGIN_NAME
- script:
- - git fetch --unshallow || echo "Already unshallowed"
- - ./gradlew -Dsonar.login=$SONAR_TOKEN sonarqube
- needs: ["test"]
- dependencies:
- - test
- only:
- refs:
- - $CI_DEFAULT_BRANCH
- variables:
- - $SONAR_TOKEN =~ /[0-9a-z]+/
-
-GitLab Maven repo:
- stage: deploy
- environment:
- name: GitLab.com / Maven packages
- url: $CI_PROJECT_URL/-/packages
- script:
- - ./gradlew publishAllPublicationsToGitlabRepository
- needs: ["build", "compile against min JOSM", "compile against latest JOSM"]
- rules:
- - if: '$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG != null && $CI_PIPELINE_SOURCE != "schedule"'
- when: always
- cache:
- <<: *global_cache
- policy: pull
-
-
-#################
-# Release stage #
-#################
-
-release:
- stage: release
- environment:
- name: pages branch / dist directory
- url: ${CI_PAGES_URL}/${CI_PROJECT_NAME}
- script:
- - &clone_pages_branch |
- echo "$SSH_PRIVATE_DEPLOY_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- git clone --depth 1 --branch pages git@${CI_SERVER_HOST}:${CI_PROJECT_PATH} pages
- - ¤t_version |
- version=`git describe --always --dirty`
- longVersion=`git describe --always --long --dirty`
- commitMessage="Release version $longVersion"
- - |
- #mkdir -pv "pages/public/dist/$version"
- #cp -v "build/dist/"* "build/tmp/jar/MANIFEST.MF" "pages/public/dist/$version"
- rm -fv "pages/public/dist/latest"
- ln -s "./$version" "pages/public/dist/latest"
- - &push_pages_branch |
- cd pages/
- git config user.name "Deploy with GitLab CI"
- git config user.email "${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}@${CI_SERVER_HOST}"
- git stage .
- git commit -a -m "$commitMessage"
- git push origin pages
- needs: ["build", "compile against min JOSM", "compile against latest JOSM"]
- dependencies:
- - build
- rules:
- - if: '$SSH_PRIVATE_DEPLOY_KEY != null && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG != null && $CI_PIPELINE_SOURCE != "schedule"'
- when: manual
- cache:
- <<: *global_cache
- policy: pull
-
-release hotfix:
- stage: release
- environment:
- name: pages branch / dist directory
- url: ${CI_PAGES_URL}/${CI_PROJECT_NAME}
- script:
- - *clone_pages_branch
- - *current_version
- - |
- mkdir -pv "pages/public/dist/$version"
- cp -v "build/dist/"* "build/tmp/jar/MANIFEST.MF" "pages/public/dist/$version"
- - *push_pages_branch
- needs: ["compile against min JOSM", "compile against latest JOSM", "build"]
- dependencies:
- - build
- rules:
- - if: '$SSH_PRIVATE_DEPLOY_KEY != null && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG != null && $CI_PIPELINE_SOURCE != "schedule" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
- when: always
-
-publish update site:
- stage: release
- environment:
- name: pages branch / snapshot update site
- url: ${CI_PAGES_URL}/${CI_PROJECT_NAME}/snapshot/${CI_COMMIT_REF_NAME}/update-site
- script:
- - *clone_pages_branch
- - |
- commitHash=`git rev-parse HEAD`
- commitMessage="Make latest commit from ${CI_COMMIT_REF_NAME} available via JOSM update site ($commitHash)"
- - |
- rm -vrf "pages/public/snapshot/${CI_COMMIT_REF_NAME}"
- mkdir -pv "pages/public/snapshot/${CI_COMMIT_REF_NAME}"
- rm -vrf "pages/public/snapshot/libs"
- mkdir -pv "pages/public/snapshot/libs"
- cp -v "build/snapshot-update-site" "pages/public/snapshot/${CI_COMMIT_REF_NAME}/update-site"
- cp -v "build/localDist/list" "pages/public/snapshot/${CI_COMMIT_REF_NAME}/update-site"
- sed -i "1s/.*/${PLUGIN_JAR_BASE_NAME}-dev.jar;${CI_PAGES_URL}/${CI_PROJECT_NAME}/snapshot/${CI_COMMIT_REF_NAME}/${PLUGIN_JAR_BASE_NAME}-dev.jar"
- cp -v "build/dist/"* "pages/public/snapshot/${CI_COMMIT_REF_NAME}"
- cp -v "build/dist/${PLUGIN_JAR_BASE_NAME}.jar" "pages/public/snapshot/${CI_COMMIT_REF_NAME}/${PLUGIN_JAR_BASE_NAME}-dev.jar"
- cp -v "build/libs/"*"test-fixture"* "pages/public/snapshot/libs/mapwithai-test-fixture.jar"
- - *push_pages_branch
- needs: ["compile against min JOSM", "compile against latest JOSM", "build"]
- dependencies:
- - build
- rules:
- - if: '$SSH_PRIVATE_DEPLOY_KEY != null && $CI_PIPELINE_SOURCE != "schedule" && $CI_COMMIT_REF_NAME != null'
- when: always
-
-release to Gitlab.com:
- stage: release
- environment:
- name: GitLab.com / Releases
- url: $CI_PROJECT_URL/-/releases
- script:
- - ./gradlew releaseToGitlab
- needs: ["GitLab Maven repo"]
- rules:
- - if: '$SSH_PRIVATE_DEPLOY_KEY != null && $CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_TAG != null && $CI_PIPELINE_SOURCE != "schedule" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
- when: always
- cache:
- <<: *global_cache
- policy: pull
+ - pages
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
deleted file mode 100644
index 5aab2e26..00000000
--- a/.gitlab/CODEOWNERS
+++ /dev/null
@@ -1 +0,0 @@
-* @smocktaylor
diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md
deleted file mode 100644
index 914d8508..00000000
--- a/.gitlab/issue_templates/bug.md
+++ /dev/null
@@ -1,40 +0,0 @@
-Summary
-
-(Summarize the bug encountered concisely)
-
-
-Steps to reproduce
-
-(How one can reproduce the issue - this is very important)
-
-
-What is the current bug behavior?
-
-(What actually happens)
-
-
-What is the expected correct behavior?
-
-(What you should see instead)
-
-JOSM Status Report
-```
-(Replace this with the JOSM Status Report (Help -> Show Status Report))
-```
-
-Relevant logs and/or screenshots
-
-(Paste any relevant logs - please use code blocks (```) to format console output,
-logs, and code as it's very hard to read otherwise.)
-
-
-Possible fixes
-
-(If you can, link to the line of code that might be responsible for the problem)
-
-/label ~bug ~needs-investigation
-/cc @smocktaylor
-/assign @smocktaylor
-/due in 1 week
-/todo
-
diff --git a/gh-pages/.nojekyll b/.nojekyll
similarity index 100%
rename from gh-pages/.nojekyll
rename to .nojekyll
diff --git a/.project b/.project
deleted file mode 100644
index d184dbb0..00000000
--- a/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- JOSM-MapWithAI
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
deleted file mode 100644
index d013a703..00000000
--- a/.settings/org.eclipse.jdt.core.prefs
+++ /dev/null
@@ -1,117 +0,0 @@
-#
-#Thu Sep 26 10:39:11 MDT 2019
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
-org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
-org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
-org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
-org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
-org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
-org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
-org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
-org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
-org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
-org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
-org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
-org.eclipse.jdt.core.compiler.problem.APILeak=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
-org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
-org.eclipse.jdt.core.compiler.debug.lineNumber=generate
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
-org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
-org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
-org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
-org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
-org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.deprecation=warning
-org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
-org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
-org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
-org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
-org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
-org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
-org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
-org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
-org.eclipse.jdt.core.compiler.problem.unusedImport=warning
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
-org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
-org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=enabled
-org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
-org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
-org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
-org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
-org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
-org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
-org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
-org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
-org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
-org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
-org.eclipse.jdt.core.compiler.problem.nullReference=warning
-org.eclipse.jdt.core.compiler.source=1.8
-org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
-org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
-org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
-org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
-org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
-org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
-org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
-org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
-org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
-org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
-org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
-org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
-org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
-org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
-org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
-eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
-org.eclipse.jdt.core.compiler.problem.deadCode=warning
-org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
-org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
-org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
-org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
-org.eclipse.jdt.core.compiler.debug.sourceFile=generate
-org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
-org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
-org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
-org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
-org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
-org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
-org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
-org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
-org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
-org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
-org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
-org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
-org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
-org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
-org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
-org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
-org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
-org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
-org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
-org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
-org.eclipse.jdt.core.compiler.compliance=1.8
-org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
-org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
-org.eclipse.jdt.core.compiler.debug.localVariable=generate
-org.eclipse.jdt.core.compiler.release=disabled
-org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
-org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
-org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
-org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
-org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
deleted file mode 100644
index e005f4eb..00000000
--- a/.settings/org.eclipse.jdt.ui.prefs
+++ /dev/null
@@ -1,61 +0,0 @@
-eclipse.preferences.version=1
-editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
-sp_cleanup.add_default_serial_version_id=true
-sp_cleanup.add_generated_serial_version_id=false
-sp_cleanup.add_missing_annotations=true
-sp_cleanup.add_missing_deprecated_annotations=true
-sp_cleanup.add_missing_methods=false
-sp_cleanup.add_missing_nls_tags=false
-sp_cleanup.add_missing_override_annotations=true
-sp_cleanup.add_missing_override_annotations_interface_methods=true
-sp_cleanup.add_serial_version_id=false
-sp_cleanup.always_use_blocks=true
-sp_cleanup.always_use_parentheses_in_expressions=false
-sp_cleanup.always_use_this_for_non_static_field_access=false
-sp_cleanup.always_use_this_for_non_static_method_access=false
-sp_cleanup.convert_functional_interfaces=false
-sp_cleanup.convert_to_enhanced_for_loop=false
-sp_cleanup.correct_indentation=true
-sp_cleanup.format_source_code=true
-sp_cleanup.format_source_code_changes_only=true
-sp_cleanup.insert_inferred_type_arguments=false
-sp_cleanup.make_local_variable_final=false
-sp_cleanup.make_parameters_final=false
-sp_cleanup.make_private_fields_final=true
-sp_cleanup.make_type_abstract_if_missing_method=false
-sp_cleanup.make_variable_declarations_final=false
-sp_cleanup.never_use_blocks=false
-sp_cleanup.never_use_parentheses_in_expressions=true
-sp_cleanup.on_save_use_additional_actions=true
-sp_cleanup.organize_imports=true
-sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
-sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
-sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
-sp_cleanup.remove_private_constructors=true
-sp_cleanup.remove_redundant_modifiers=false
-sp_cleanup.remove_redundant_semicolons=true
-sp_cleanup.remove_redundant_type_arguments=false
-sp_cleanup.remove_trailing_whitespaces=true
-sp_cleanup.remove_trailing_whitespaces_all=true
-sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
-sp_cleanup.remove_unnecessary_casts=true
-sp_cleanup.remove_unnecessary_nls_tags=false
-sp_cleanup.remove_unused_imports=true
-sp_cleanup.remove_unused_local_variables=false
-sp_cleanup.remove_unused_private_fields=true
-sp_cleanup.remove_unused_private_members=false
-sp_cleanup.remove_unused_private_methods=true
-sp_cleanup.remove_unused_private_types=true
-sp_cleanup.sort_members=false
-sp_cleanup.sort_members_all=false
-sp_cleanup.use_anonymous_class_creation=false
-sp_cleanup.use_blocks=true
-sp_cleanup.use_blocks_only_for_return_and_throw=false
-sp_cleanup.use_lambda=true
-sp_cleanup.use_parentheses_in_expressions=false
-sp_cleanup.use_this_for_non_static_field_access=false
-sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
-sp_cleanup.use_this_for_non_static_method_access=false
-sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index d159169d..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,339 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
-
- Copyright (C)
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- , 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
diff --git a/README.md b/README.md
deleted file mode 100644
index c3f9c84b..00000000
--- a/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# JOSM MapWithAI Plugin (formerly RapiD Plugin)
-
-[](https://github.com/JOSM/MapWithAI/actions/workflows/ant.yml)
-[](https://codecov.io/github/gokaart/JOSM_MapWithAI?branch=master)
-[](./LICENSE)
-
-This plugin brings MapWithAI information into JOSM.
-
-
-## Installation
-
-To use this plugin, [install JOSM](https://josm.openstreetmap.de/wiki/Download) and then [in the preferences menu install the **MapWithAI** plugin](https://josm.openstreetmap.de/wiki/Help/Preferences/Plugins#AutomaticinstallationviaPreferencesmenu)
-
-# How to use the plugin
-See the [wiki page](https://josm.openstreetmap.de/wiki/Help/Plugin/MapWithAI).
-
-## Information
-* [RapiD](https://mapwith.ai/rapid)
-* [HOT Tasking Manager + RapiD](https://tasks-assisted.hotosm.org/)
-* [RapiD source code and country requests](https://github.com/facebookincubator/RapiD)
-* [mapwith.ai](https://mapwith.ai/)
-
-## Contributing
-
-- The **source code** is hosted on [GitHub](https://github.com/JOSM/MapWithAI).
-- **Issues** are managed in [JOSM Trac](https://josm.openstreetmap.de/query?status=assigned&status=needinfo&status=new&status=reopened&component=Plugin+mapwithai&group=component&max=200&col=id&col=summary&col=component&col=status&col=type&col=priority&order=priority&report=17)
- - Report a [New Ticket](https://josm.openstreetmap.de/newticket?component=Plugin+mapwithai)
-- **Translations** are not currently done.
-
-## Authors
-
-- Taylor Smock (taylor.smock)
-
-## License
-
-GPLv2 or later (same as JOSM)
diff --git a/_redirects b/_redirects
new file mode 100644
index 00000000..76146bb1
--- /dev/null
+++ b/_redirects
@@ -0,0 +1,2 @@
+/JOSM_MapWithAI/dist/v2.0.0-alpha.44/Mapillary.jar https://gitlab.com/smocktaylor/JOSM_MapWithAI/-/package_files/21357258/download 200
+/JOSM_MapWithAI/dist/v2.0.0-alpha.36/Mapillary.jar https://github.com/JOSM/Mapillary/releases/download/v2.0.0-alpha.36/Mapillary.jar 302
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 706b65c2..00000000
--- a/build.gradle
+++ /dev/null
@@ -1,312 +0,0 @@
-import groovy.xml.XmlParser
-
-plugins {
- id "com.diffplug.spotless" version "6.25.0"
- id "com.github.ben-manes.versions" version "0.51.0"
- id "com.github.spotbugs" version "6.0.8"
- // id "de.aaschmid.cpd" version "3.3"
- id "eclipse"
- id "jacoco"
- id "java"
- id "java-test-fixtures" /* Used for publishing test fixtures package */
- id "maven-publish"
- id "net.ltgt.errorprone" version "3.1.0"
- id "org.openstreetmap.josm" version "0.8.2"
- id "org.sonarqube" version "4.4.1.3373"
- id "pmd"
-}
-
-archivesBaseName = "mapwithai"
-def gitlabGroup = "gokaart"
-def gitlabRepositoryName = "JOSM_MapWithAI"
-
-repositories {
- mavenCentral()
- maven {
- url "https://josm.openstreetmap.de/nexus/content/repositories/releases/"
- }
-}
-
-def versions = [
- awaitility: "4.2.0",
- equalsverifier: "3.15.8",
- errorprone: "2.26.0",
- findsecbugs: "1.13.0",
- jacoco: "0.8.10",
- jmockit: "1.49.a",
- josm: properties.get("plugin.compile.version"),
- junit: "5.10.2",
- pmd: "6.20.0",
- spotbugs: "4.8.3",
- wiremock: "2.35.0",
-]
-
-dependencies {
- spotbugsPlugins "com.h3xstream.findsecbugs:findsecbugs-plugin:${versions.findsecbugs}"
- errorprone("com.google.errorprone:error_prone_core:${versions.errorprone}")
-
- testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:${versions.junit}")
- testFixturesRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${versions.junit}")
- testFixturesImplementation("org.junit.vintage:junit-vintage-engine:${versions.junit}")
- testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:${versions.junit}")
- testFixturesImplementation("org.jmockit:jmockit:${versions.jmockit}")
- testFixturesImplementation("com.github.spotbugs:spotbugs-annotations:${versions.spotbugs}")
- testFixturesImplementation("org.openstreetmap.josm:josm:${versions.josm}")
- testFixturesImplementation("org.openstreetmap.josm:josm-unittest:"){changing=true}
- testFixturesImplementation("com.github.tomakehurst:wiremock-jre8:${versions.wiremock}")
- testFixturesImplementation("org.awaitility:awaitility:${versions.awaitility}")
- testImplementation("nl.jqno.equalsverifier:equalsverifier:${versions.equalsverifier}")
-}
-
-configurations {
- testImplementation.extendsFrom testFixturesImplementation
- testRuntimeOnly.extendsFrom testFixturesRuntimeOnly
- intTestRuntimeOnly.extendsFrom testRuntimeOnly
- intTestImplementation.extendsFrom testImplementation
-}
-
-int getJavaVersion() {
- // We want to use whatever Java version CI has as default
- def ci = project.hasProperty("isCI") or project.hasProperty("CI") or System.getenv("CI") != null
- // But we want to override if someone set a specific Java version
- def javaVersion = System.getenv("JAVA_VERSION")?.isInteger() ? Integer.valueOf(System.getenv("JAVA_VERSION")) : null
- if (javaVersion != null) {
- return javaVersion
- }
- if (ci) {
- return Integer.valueOf(JavaVersion.current().getMajorVersion())
- }
- return 17
-}
-
-logger.lifecycle("Using Java " + getJavaVersion())
-
-java {
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(getJavaVersion()))
- }
-}
-
-// Set up Errorprone
-tasks.withType(JavaCompile).configureEach {
- options.errorprone {
- error(
- "ClassCanBeStatic",
- "DefaultCharset",
- "ReferenceEquality",
- "WildcardImport"
- )
- warn(
- "ConstantField",
- "FieldCanBeFinal",
- "LambdaFunctionalInterface",
- "MethodCanBeStatic",
- "MultiVariableDeclaration",
- "PrivateConstructorForUtilityClass",
- "RemoveUnusedImports",
- "UngroupedOverloads"
- )
- }
-}
-
-rootProject.tasks.named("jar") {
- duplicatesStrategy = 'include'
-}
-
-
-sourceSets {
- test {
- java {
- srcDirs = ["src/test/unit"]
- }
- resources {
- srcDirs = ["src/test/resources"]
- }
- }
- testFixtures {
- java {
- srcDirs = ["src/test/unit"]
- setIncludes(new HashSet(['org/openstreetmap/josm/plugins/mapwithai/testutils/**/*.java']))
- }
- resources {
- srcDirs = ["src/test/resources"]
- }
- }
- intTest {
- compileClasspath += sourceSets.main.output
- compileClasspath += sourceSets.test.output
- runtimeClasspath += sourceSets.main.output
- runtimeClasspath += sourceSets.test.output
- java {
- srcDirs = ["src/test/integration"]
- }
- resources {
- srcDirs = ["src/test/resources"]
- }
- }
-}
-
-
-test {
- project.afterEvaluate {
- jvmArgs("-javaagent:${classpath.find { it.name.contains("jmockit") }.absolutePath}")
- jvmArgs("-Djunit.jupiter.extensions.autodetection.enabled=true")
- jvmArgs("-Djava.awt.headless=true")
- }
- useJUnitPlatform()
- ignoreFailures
- testLogging {
- exceptionFormat "full"
- events "skipped", "failed"
- info {
- showStandardStreams true
- }
- }
-}
-
-task integrationTest(type: Test) {
- description = "Run integration tests"
- group = "verification"
-
- testClassesDirs = sourceSets.intTest.output.classesDirs
- classpath = sourceSets.intTest.runtimeClasspath
- shouldRunAfter test
- // Ignore failures -- servers may or may not be down
- ignoreFailures = true
-}
-
-check.dependsOn integrationTest
-
-tasks.processResources {
- // Note: src/${source_set}/resources is automatically copied
- // processResources uses the `main` source set.
- // https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_resources
- from("$projectDir/LICENSE")
- from("$projectDir/README.md")
-}
-
-jacocoTestCoverageVerification {
- violationRules {
- rule {
- limit {
- minimum = 0.80
- }
- }
- }
-}
-
-spotless {
- java {
- eclipse().configFile "config/josm_formatting.xml"
- endWithNewline()
- importOrder('javax', 'java', 'org', 'com', '')
- indentWithSpaces(4)
- licenseHeader "// License: GPL. For details, see LICENSE file."
- ratchetFrom("origin/master")
- removeUnusedImports()
- trimTrailingWhitespace()
- }
-}
-
-josm {
- debugPort = 7055
- manifest {
- setMinJavaVersion 17
- oldVersionDownloadLink 18218, "v1.9.20", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.9.20/mapwithai.jar")
- oldVersionDownloadLink 17903, "v1.8.7", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.8.7/mapwithai.jar")
- oldVersionDownloadLink 17084, "v1.7.1.6", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.7.1.6/mapwithai.jar")
- oldVersionDownloadLink 16645, "v1.6.8", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.6.8/mapwithai.jar")
- oldVersionDownloadLink 16284, "v1.5.10", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.5.10/mapwithai.jar")
- oldVersionDownloadLink 16220, "v1.4.7", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.4.7/mapwithai.jar")
- oldVersionDownloadLink 15820, "v1.3.11", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.3.11/mapwithai.jar")
- oldVersionDownloadLink 15737, "v1.2.7", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.2.7/mapwithai.jar")
- oldVersionDownloadLink 15609, "v1.1.12", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.1.12/mapwithai.jar")
- oldVersionDownloadLink 15542, "v1.0.9", new URL("https://github.com/JOSM/MapWithAI/releases/download/v1.0.9/mapwithai.jar")
- oldVersionDownloadLink 15233, "v0.2.14", new URL("https://github.com/JOSM/MapWithAI/releases/download/v0.2.14/mapwithai.jar")
- }
- i18n {
- pathTransformer = getPathTransformer(project.projectDir, "gitlab.com/${gitlabGroup}/${gitlabRepositoryName}/blob")
- }
-}
-
-tasks.withType(JavaCompile) {
- options.compilerArgs += [
- "-Xlint:all",
- "-Xlint:-serial",
- ]
-}
-
-// Set up JaCoCo
-jacoco {
- toolVersion = "${versions.jacoco}"
-}
-jacocoTestReport {
- dependsOn test
- reports {
- xml.required.set(true)
- html.required.set(true)
- }
-}
-check.dependsOn jacocoTestReport
-
-// Set up PMD
-pmd {
- toolVersion = versions.pmd
- ignoreFailures true
- incrementalAnalysis = true
- ruleSets = []
- ruleSetConfig = resources.text.fromFile("$projectDir/config/pmd/ruleset.xml")
- sourceSets = [sourceSets.main]
-}
-
-// Set up SpotBugs
-spotbugs {
- toolVersion = versions.spotbugs
- ignoreFailures = true
-}
-spotbugsMain {
- reports {
- xml.required.set(false)
- html.required.set(true)
- }
-}
-
-publishing {
- publications {
- maven(MavenPublication) {
- groupId = "org.openstreetmap.josm.plugins"
- artifactId = archivesBaseName
- version = project.version
-
- from components.java
- }
- }
-}
-
-def ciJobToken = System.getenv("CI_JOB_TOKEN")
-def projectId = System.getenv("CI_PROJECT_ID")
-if (ciJobToken != null && projectId!= null) {
- publishing.repositories.maven {
- url = "https://gitlab.com/api/v4/projects/$projectId/packages/maven"
- name = "gitlab"
- credentials(HttpHeaderCredentials.class) {
- name = "Job-Token"
- value = ciJobToken
- }
- authentication {
- create("auth", HttpHeaderAuthentication.class)
- }
- }
-}
-
-sonarqube {
- properties {
- property "sonar.organization", "mapwithai"
- property "sonar.projectKey", "mapwithai"
- property "sonar.forceAuthentication", "true"
- property "sonar.host.url", "https://sonarcloud.io"
- property "sonar.projectDescription", properties.get("plugin.description")
- property "sonar.projectVersion", project.version
- property "sonar.sources", ["src"]
- }
-}
diff --git a/build.xml b/build.xml
deleted file mode 100644
index d8bfa8fd..00000000
--- a/build.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index 901bc091..00000000
--- a/codecov.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-coverage:
- range: 50..100
-fixes:
- - "project/::src/"
diff --git a/config/josm_formatting.xml b/config/josm_formatting.xml
deleted file mode 100644
index 129aad7c..00000000
--- a/config/josm_formatting.xml
+++ /dev/null
@@ -1,365 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/config/pmd/ruleset.xml b/config/pmd/ruleset.xml
deleted file mode 100644
index 5d71904f..00000000
--- a/config/pmd/ruleset.xml
+++ /dev/null
@@ -1,197 +0,0 @@
-
-
-
- This ruleset checks some rules that you should normally follow for the JOSM-mapwithai plugin.
- Copied from josm tools/pmd/josm-ruleset.xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/fonts/Roboto-Light.ttf b/fonts/Roboto-Light.ttf
new file mode 100644
index 00000000..94c6bcc6
Binary files /dev/null and b/fonts/Roboto-Light.ttf differ
diff --git a/gh-pages/index.html b/gh-pages/index.html
deleted file mode 100644
index a1e7d322..00000000
--- a/gh-pages/index.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-RapiD-plugin for JOSM
-
-
-
Developer resources
-
-
diff --git a/gh-pages/style/default.css b/gh-pages/style/default.css
deleted file mode 100644
index 25925ecb..00000000
--- a/gh-pages/style/default.css
+++ /dev/null
@@ -1,42 +0,0 @@
-* {
- box-sizing: border-box;
-}
-body {
- font-family: sans-serif;
- background-color: #333;
- color:white;
- font-size: 18px;
-}
-h1,h2 {
- font-weight:100;
-}
-h1 {
- font-size:2.5em;
-}
-h2 {
- font-size:1.5em;
-}
-.container {
- text-align: center;
-}
-ul {
- padding:0;
-}
-ul li {
- list-style: none;
-}
-.container a {
- color:#ddd;
- text-decoration: none;
- border:1px solid white;
- padding:.75em;
- margin:.5em;
- display:inline-block;
- box-shadow: 0 0 0 #36af6d;
- transition:.25s ease box-shadow, .5s ease border-color, .5s ease color;
-}
-a:hover {
- color:#fff;
- border-color: #36af6d;
- box-shadow: 0 0 8px #36af6d;
-}
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index 5c5f4494..00000000
--- a/gradle.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# The minimum JOSM version this plugin is compatible with (can be any numeric version
-plugin.main.version = 19067
-# The JOSM version this plugin is currently compiled against
-# Please make sure this version is available at https://josm.openstreetmap.de/download
-# The special values "latest" and "tested" are also possible here, but not recommended.
-plugin.compile.version = 19067
-plugin.canloadatruntime = true
-plugin.author = Taylor Smock
-plugin.class = org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin
-plugin.icon = images/dialogs/mapwithai.svg
-plugin.link = https://github.com/JOSM/MapWithAI
-plugin.minimum.java.version = 17
-plugin.description = Allows the use of MapWithAI data in JOSM (same data as used in RapiD)
-
-plugin.requires = pmtiles;utilsplugin2
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index 033e24c4..00000000
Binary files a/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index 4baf5a11..00000000
--- a/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,8 +0,0 @@
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
deleted file mode 100755
index fcb6fca1..00000000
--- a/gradlew
+++ /dev/null
@@ -1,248 +0,0 @@
-#!/bin/sh
-
-#
-# Copyright © 2015-2021 the original authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-##############################################################################
-#
-# Gradle start up script for POSIX generated by Gradle.
-#
-# Important for running:
-#
-# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
-# noncompliant, but you have some other compliant shell such as ksh or
-# bash, then to run this script, type that shell name before the whole
-# command line, like:
-#
-# ksh Gradle
-#
-# Busybox and similar reduced shells will NOT work, because this script
-# requires all of these POSIX shell features:
-# * functions;
-# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
-# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
-# * compound commands having a testable exit status, especially «case»;
-# * various built-in commands including «command», «set», and «ulimit».
-#
-# Important for patching:
-#
-# (2) This script targets any POSIX shell, so it avoids extensions provided
-# by Bash, Ksh, etc; in particular arrays are avoided.
-#
-# The "traditional" practice of packing multiple parameters into a
-# space-separated string is a well documented source of bugs and security
-# problems, so this is (mostly) avoided, by progressively accumulating
-# options in "$@", and eventually passing that to Java.
-#
-# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
-# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
-# see the in-line comments for details.
-#
-# There are tweaks for specific operating systems such as AIX, CygWin,
-# Darwin, MinGW, and NonStop.
-#
-# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
-# within the Gradle project.
-#
-# You can find Gradle at https://github.com/gradle/gradle/.
-#
-##############################################################################
-
-# Attempt to set APP_HOME
-
-# Resolve links: $0 may be a link
-app_path=$0
-
-# Need this for daisy-chained symlinks.
-while
- APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
- [ -h "$app_path" ]
-do
- ls=$( ls -ld "$app_path" )
- link=${ls#*' -> '}
- case $link in #(
- /*) app_path=$link ;; #(
- *) app_path=$APP_HOME$link ;;
- esac
-done
-
-# This is normally unused
-# shellcheck disable=SC2034
-APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD=maximum
-
-warn () {
- echo "$*"
-} >&2
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-} >&2
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "$( uname )" in #(
- CYGWIN* ) cygwin=true ;; #(
- Darwin* ) darwin=true ;; #(
- MSYS* | MINGW* ) msys=true ;; #(
- NONSTOP* ) nonstop=true ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD=$JAVA_HOME/jre/sh/java
- else
- JAVACMD=$JAVA_HOME/bin/java
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD=java
- if ! command -v java >/dev/null 2>&1
- then
- die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-fi
-
-# Increase the maximum file descriptors if we can.
-if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
- case $MAX_FD in #(
- max*)
- # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
- MAX_FD=$( ulimit -H -n ) ||
- warn "Could not query maximum file descriptor limit"
- esac
- case $MAX_FD in #(
- '' | soft) :;; #(
- *)
- # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
- ulimit -n "$MAX_FD" ||
- warn "Could not set maximum file descriptor limit to $MAX_FD"
- esac
-fi
-
-# Collect all arguments for the java command, stacking in reverse order:
-# * args from the command line
-# * the main class name
-# * -classpath
-# * -D...appname settings
-# * --module-path (only if needed)
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
-
-# For Cygwin or MSYS, switch paths to Windows format before running java
-if "$cygwin" || "$msys" ; then
- APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
- CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
-
- JAVACMD=$( cygpath --unix "$JAVACMD" )
-
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- for arg do
- if
- case $arg in #(
- -*) false ;; # don't mess with options #(
- /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
- [ -e "$t" ] ;; #(
- *) false ;;
- esac
- then
- arg=$( cygpath --path --ignore --mixed "$arg" )
- fi
- # Roll the args list around exactly as many times as the number of
- # args, so each arg winds up back in the position where it started, but
- # possibly modified.
- #
- # NB: a `for` loop captures its iteration list before it begins, so
- # changing the positional parameters here affects neither the number of
- # iterations, nor the values presented in `arg`.
- shift # remove old arg
- set -- "$@" "$arg" # push replacement arg
- done
-fi
-
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
-
-set -- \
- "-Dorg.gradle.appname=$APP_BASE_NAME" \
- -classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
- "$@"
-
-# Stop when "xargs" is not available.
-if ! command -v xargs >/dev/null 2>&1
-then
- die "xargs is not available"
-fi
-
-# Use "xargs" to parse quoted args.
-#
-# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
-#
-# In Bash we could simply go:
-#
-# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
-# set -- "${ARGS[@]}" "$@"
-#
-# but POSIX shell has neither arrays nor command substitution, so instead we
-# post-process each arg (as a line of input to sed) to backslash-escape any
-# character that might be a shell metacharacter, then use eval to reverse
-# that process (while maintaining the separation between arguments), and wrap
-# the whole thing up as a single "set" statement.
-#
-# This will of course break if any of these variables contains a newline or
-# an unmatched quote.
-#
-
-eval "set -- $(
- printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
- xargs -n1 |
- sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
- tr '\n' ' '
- )" '"$@"'
-
-exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
deleted file mode 100644
index 6689b85b..00000000
--- a/gradlew.bat
+++ /dev/null
@@ -1,92 +0,0 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%"=="" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
diff --git a/img/bg.png b/img/bg.png
new file mode 100644
index 00000000..5bb626a0
Binary files /dev/null and b/img/bg.png differ
diff --git a/img/josm_logo.svg b/img/josm_logo.svg
new file mode 100644
index 00000000..b3eedc2e
--- /dev/null
+++ b/img/josm_logo.svg
@@ -0,0 +1,208 @@
+
+
+ JOSM Logotype 2019
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ image/svg+xml
+
+ JOSM Logotype 2019
+ 2019-08-05
+
+
+ Diamond00744
+
+
+
+
+ Public Domain
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/images/dialogs/mapwithai.svg b/img/mapwithai.svg
similarity index 100%
rename from src/main/resources/images/dialogs/mapwithai.svg
rename to img/mapwithai.svg
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..58e1f512
--- /dev/null
+++ b/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+MapWithAI-plugin for JOSM
+
+
+
+
diff --git a/json/blacklisted_versions.json b/json/blacklisted_versions.json
new file mode 100644
index 00000000..fe51488c
--- /dev/null
+++ b/json/blacklisted_versions.json
@@ -0,0 +1 @@
+[]
diff --git a/json/conflation_servers.json b/json/conflation_servers.json
new file mode 100644
index 00000000..a14cb507
--- /dev/null
+++ b/json/conflation_servers.json
@@ -0,0 +1,11 @@
+{
+ "Taylor's Address Conflation Server" : {
+ "categories" : [
+ "addresses"
+ ],
+ "description" : "Originally developed for use with local datasets, it now accepts external datasets for conflation purposes. In the event of a failure, the plugin will use the original dataset.",
+ "license" : "AGPL",
+ "source" : "https://gitlab.com/smocktaylor/serve_osm_files/",
+ "url" : "https://importdata.riverviewtechnologies.com/conflate"
+ }
+}
diff --git a/json/sources.json b/json/sources.json
new file mode 100644
index 00000000..31ddace1
--- /dev/null
+++ b/json/sources.json
@@ -0,0 +1,308 @@
+{
+ "(Update JOSM and MapWithAI Plugin -- currently only supported on development builds) Esri OpenStreetMap Curated Data": {
+ "type": "esri",
+ "url": "https://openstreetmap.maps.arcgis.com/sharing/rest/",
+ "id": "bdf6c800b3ae453b9db239e03d7c1727",
+ "source": "esri",
+ "conflate": true,
+ "conflation_ignore_categories": ["addresses"],
+ "conflationUrl": "https://rapideditor.org/maps/ml_roads?bbox={bbox}&building_source=esri&esri_id={id}",
+ "conflationParameters": [{
+ "description": "buildings",
+ "enabled": true,
+ "parameter": "result_type=road_building_vector_xml"
+ },
+ {
+ "description": "Conflate with OpenStreetMap",
+ "enabled": true,
+ "parameter": "conflate_with_osm=true"
+ },
+ {
+ "description": "Type of dataset (theme)",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "theme=ml_road_vector"
+ },
+ {
+ "description": "MapWithAI Collaborator",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "collaborator=josm"
+ },
+ {
+ "description": "MapWithAI Token",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "token=ASb3N5o9HbX8QWn8G_NtHIRQaYv3nuG2r7_f3vnGld3KhZNCxg57IsaQyssIaEw5rfRNsPpMwg4TsnrSJtIJms5m"
+ },
+ {
+ "description": "MapWithAI Hash",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "hash=ASawRla3rBcwEjY4HIY"
+ }]
+ },
+ "Overture": {
+ "type": "overture",
+ "url": "https://overturemaps-tiles-us-west-2-beta.s3.us-west-2.amazonaws.com/pmtiles_catalog.json",
+ "license": "https://docs.overturemaps.org/attribution/#places",
+ "category": "preview",
+ "provider": "Overture"
+ },
+ "Statewide Aggregate Addresses in Colorado 2019 (Public)": {
+ "id": "colorado_public_aggregate_addresses",
+ "type": "thirdParty",
+ "category": "addresses",
+ "provider": "State of Colorado",
+ "countries": {
+ "US-CO": ["addr:housenumber"]
+ },
+ "license": "Public Domain",
+ "osm_compatible": "yes",
+ "parameters": [],
+ "permission_url": "https://wiki.openstreetmap.org/wiki/Import/Colorado_Addresses",
+ "url": "https://importdata.riverviewtechnologies.com/coloradoAddresses/map?bbox={bbox}"
+ },
+ "MapWithAI": {
+ "id": "facebook_mapwithai",
+ "type": "facebook",
+ "category": "highways;buildings;featured",
+ "provider": "Facebook",
+ "countries": {
+ "AE": ["highway"],
+ "AF": ["highway"],
+ "AG": ["highway"],
+ "AI": ["highway"],
+ "AL": ["highway"],
+ "AM": ["highway"],
+ "AO": ["highway"],
+ "AR": ["highway"],
+ "AT": ["highway"],
+ "AU": ["highway"],
+ "AZ": ["highway"],
+ "BA": ["highway"],
+ "BB": ["highway"],
+ "BD": ["highway"],
+ "BE": ["highway"],
+ "BF": ["highway"],
+ "BG": ["highway"],
+ "BI": ["highway"],
+ "BJ": ["highway"],
+ "BL": ["highway"],
+ "BN": ["highway"],
+ "BO": ["highway"],
+ "BR": ["highway"],
+ "BS": ["highway"],
+ "BT": ["highway"],
+ "BW": ["highway"],
+ "BY": ["highway"],
+ "BZ": ["highway"],
+ "CA": ["building", "highway"],
+ "CD": ["highway"],
+ "CF": ["highway"],
+ "CG": ["highway"],
+ "CH": ["highway"],
+ "CI": ["highway"],
+ "CL": ["highway"],
+ "CM": ["highway"],
+ "CN": ["highway"],
+ "CO": ["highway"],
+ "CR": ["highway"],
+ "CU": ["highway"],
+ "CY": ["highway"],
+ "CZ": ["highway"],
+ "DE": ["highway"],
+ "DJ": ["highway"],
+ "DK": ["highway"],
+ "DM": ["highway"],
+ "DO": ["highway"],
+ "DZ": ["highway"],
+ "EC": ["highway"],
+ "EE": ["highway"],
+ "EG": ["highway"],
+ "EH": ["highway"],
+ "ER": ["highway"],
+ "ES": ["highway"],
+ "ET": ["highway"],
+ "FK": ["highway"],
+ "FI": ["highway"],
+ "FJ": ["highway"],
+ "FR": ["highway"],
+ "GA": ["highway"],
+ "GB": ["highway"],
+ "GD": ["highway"],
+ "GE": ["highway"],
+ "GF": ["highway"],
+ "GH": ["highway"],
+ "GM": ["highway"],
+ "GN": ["highway"],
+ "GP": ["highway"],
+ "GQ": ["highway"],
+ "GR": ["highway"],
+ "GT": ["highway"],
+ "GW": ["highway"],
+ "GY": ["highway"],
+ "HN": ["highway"],
+ "HR": ["highway"],
+ "HT": ["highway"],
+ "HU": ["highway"],
+ "ID": ["highway"],
+ "IE": ["highway"],
+ "IL": ["highway"],
+ "IN": ["highway"],
+ "IQ": ["highway"],
+ "IS": ["highway"],
+ "IT": ["highway"],
+ "JM": ["highway"],
+ "JO": ["highway"],
+ "JP": ["highway"],
+ "KE": ["highway"],
+ "KG": ["highway"],
+ "KH": ["highway"],
+ "KN": ["highway"],
+ "KY": ["highway"],
+ "KR": ["highway"],
+ "KW": ["highway"],
+ "KZ": ["highway"],
+ "LA": ["highway"],
+ "LB": ["highway"],
+ "LC": ["highway"],
+ "LK": ["highway"],
+ "LR": ["highway"],
+ "LS": ["highway"],
+ "LT": ["highway"],
+ "LU": ["highway"],
+ "LV": ["highway"],
+ "LY": ["highway"],
+ "MA": ["highway"],
+ "MD": ["highway"],
+ "ME": ["highway"],
+ "MF": ["highway"],
+ "MG": ["highway"],
+ "MK": ["highway"],
+ "ML": ["highway"],
+ "MM": ["highway"],
+ "MN": ["highway"],
+ "MQ": ["highway"],
+ "MR": ["highway"],
+ "MS": ["highway"],
+ "MW": ["highway"],
+ "MX": ["highway"],
+ "MY": ["highway"],
+ "MZ": ["highway"],
+ "NA": ["highway"],
+ "NE": ["highway"],
+ "NG": ["highway"],
+ "NI": ["highway"],
+ "NL": ["highway"],
+ "NL-BQ2": ["highway"],
+ "NL-BQ3": ["highway"],
+ "NO": ["highway"],
+ "NP": ["highway"],
+ "NZ": ["highway"],
+ "OM": ["highway"],
+ "PA": ["highway"],
+ "PE": ["highway"],
+ "PF": ["highway"],
+ "PG": ["highway"],
+ "PH": ["highway"],
+ "PK": ["highway"],
+ "PL": ["highway"],
+ "PR": ["highway"],
+ "PS": ["highway"],
+ "PT": ["highway"],
+ "PY": ["highway"],
+ "QA": ["highway"],
+ "RO": ["highway"],
+ "RS": ["highway"],
+ "RS-KM": ["highway"],
+ "RU": ["highway"],
+ "RW": ["highway"],
+ "SA": ["highway"],
+ "SB": ["highway"],
+ "SD": ["highway"],
+ "SE": ["highway"],
+ "SG": ["highway"],
+ "SI": ["highway"],
+ "SK": ["highway"],
+ "SL": ["highway"],
+ "SN": ["highway"],
+ "SO": ["highway"],
+ "SR": ["highway"],
+ "SS": ["highway"],
+ "ST": ["highway"],
+ "SV": ["highway"],
+ "SX": ["highway"],
+ "SZ": ["highway"],
+ "TC": ["highway"],
+ "TD": ["highway"],
+ "TG": ["highway"],
+ "TH": ["highway"],
+ "TJ": ["highway"],
+ "TL": ["highway"],
+ "TM": ["highway"],
+ "TN": ["highway"],
+ "TR": ["highway"],
+ "TT": ["highway"],
+ "TW": ["highway"],
+ "TZ": ["building", "highway"],
+ "UA": ["highway"],
+ "UG": ["building", "highway"],
+ "US": ["building", "highway"],
+ "US-AK": ["building"],
+ "US-HI": ["building"],
+ "UY": ["highway"],
+ "UZ": ["highway"],
+ "VC": ["highway"],
+ "VE": ["highway"],
+ "VG": ["highway"],
+ "VN": ["highway"],
+ "VU": ["highway"],
+ "YE": ["highway"],
+ "ZA": ["highway"],
+ "ZM": ["highway"],
+ "ZW": ["highway"]
+ },
+ "default": true,
+ "license": "ODBL",
+ "osm_compatible": "yes",
+ "parameters": [{
+ "description": "buildings",
+ "enabled": true,
+ "parameter": "result_type=road_building_vector_xml"
+ },
+ {
+ "description": "Conflate with OpenStreetMap",
+ "enabled": true,
+ "parameter": "conflate_with_osm=true"
+ },
+ {
+ "description": "Type of dataset (theme)",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "theme=ml_road_vector"
+ },
+ {
+ "description": "MapWithAI Collaborator",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "collaborator=josm"
+ },
+ {
+ "description": "MapWithAI Token",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "token=ASb3N5o9HbX8QWn8G_NtHIRQaYv3nuG2r7_f3vnGld3KhZNCxg57IsaQyssIaEw5rfRNsPpMwg4TsnrSJtIJms5m"
+ },
+ {
+ "description": "MapWithAI Hash",
+ "enabled": true,
+ "permanent": true,
+ "parameter": "hash=ASawRla3rBcwEjY4HIY"
+ }],
+ "permission_url": "https://github.com/facebookmicrosites/Open-Mapping-At-Facebook/wiki/FAQ",
+ "terms_of_use_url": "https://rapideditor.org/doc/license/MapWithAILicense.pdf",
+ "privacy_policy_url": "https://rapideditor.org/doc/license/MapWithAIPrivacyPolicy.pdf#page=3",
+ "url": "https://rapideditor.org/maps/ml_roads?bbox={bbox}"
+ }
+}
diff --git a/pom.xml b/pom.xml
deleted file mode 100644
index 8a0a9214..00000000
--- a/pom.xml
+++ /dev/null
@@ -1,193 +0,0 @@
-
- 4.0.0
- org.openstreetmap.josm.plugins
- MapWithAI
- 1.0-SNAPSHOT
-
- 1.49.a
- 7.5.0
- 0.8.12
- 10.18.1
- 4.8.6
-
-
-
- JOSM-releases
- https://josm.openstreetmap.de/repository/releases/
-
-
- JOSM-snapshots
- https://josm.openstreetmap.de/repository/snapshots/
-
-
-
-
-
- org.junit
- junit-bom
- 5.11.1
- pom
- import
-
-
-
-
-
- org.openstreetmap.josm
- josm
- 1.5-SNAPSHOT
- provided
-
-
- org.openstreetmap.josm.plugins
- pmtiles
- 1.0-SNAPSHOT
- provided
-
-
- org.openstreetmap.josm.plugins
- utilsplugin2
- 1.0-SNAPSHOT
- provided
-
-
- org.openstreetmap.josm
- josm-unittest
- 1.5-SNAPSHOT
- test
-
-
- org.wiremock
- wiremock
- 3.9.1
- test
-
-
- org.junit.jupiter
- junit-jupiter-api
- test
-
-
- org.junit.jupiter
- junit-jupiter-params
- test
-
-
- junit
- junit
- 4.13.2
- test
-
-
- org.awaitility
- awaitility
- 4.2.2
- test
-
-
- nl.jqno.equalsverifier
- equalsverifier
- 3.17
- test
-
-
- org.jmockit
- jmockit
- 1.49.a
- test
-
-
- jakarta.json
- jakarta.json-api
- 2.1.3
- provided
-
-
-
- src/test/unit
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.13.0
-
- 17
-
-
-
- com.diffplug.spotless
- spotless-maven-plugin
- 2.43.0
-
-
-
- 4.21
- ${project.basedir}/../00_core_tools/eclipse/formatter.xml
-
-
-
- // License: GPL. For details, see LICENSE file.
-
-
- src/main/java/**/*.java
- src/test/unit/**/*.java
- src/test/integration/**/*.java
-
-
-
-
-
-
- check
-
-
-
-
-
- org.apache.maven.plugins
- maven-enforcer-plugin
- 3.5.0
-
-
- enforce-maven
-
- enforce
-
-
-
-
-
-
- 3.6.3
-
-
-
-
-
-
- maven-surefire-plugin
- 3.2.5
-
- 1
- -javaagent:"${settings.localRepository}"/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar
-
-
- file.encoding = UTF-8
- java.locale.providers = SPI,CLDR
- junit.jupiter.extensions.autodetection.enabled = true
- junit.jupiter.execution.parallel.enabled = true
-
-
-
- src/test/build/config/josm.home
- src/test/resources
- true
- Monocle
- Headless
- sw
-
-
-
-
-
-
diff --git a/scripts/sources.js b/scripts/sources.js
new file mode 100644
index 00000000..4b9bb514
--- /dev/null
+++ b/scripts/sources.js
@@ -0,0 +1,53 @@
+function addCommaSpace(text) {
+ var spacedText = "";
+ for (i of text) {
+ spacedText += i;
+ if (i == ",") {
+ spacedText += " ";
+ }
+ }
+ return spacedText;
+}
+function createTable(sources) {
+ var div = document.getElementsByClassName("mapwithaisourcetable")[0];
+ var table = document.createElement("table");
+ var tablebody = document.createElement("tbody");
+ var tablehead = document.createElement("thead");
+ var tableheadrow = document.createElement("tr");
+ for (header of ["Source", "URL", "Parameters", "Countries", "License", "OSM Usable"]) {
+ var th = document.createElement("th");
+ th.textContent = header;
+ tableheadrow.appendChild(th);
+ }
+ tablehead.appendChild(tableheadrow);
+
+ for (source in sources) {
+ var row = document.createElement("tr");
+ var sourcetd = document.createElement("td");
+ sourcetd.textContent = source;
+ row.appendChild(sourcetd);
+ for (columnName of ["url", "parameters", "countries", "license", "osm_compatible"]) {
+ var column = document.createElement("td");
+ column.textContent = sources[source][columnName];
+ if (column.textContent == "[object Object]") {
+ column.textContent = JSON.stringify(sources[source][columnName]);
+ }
+ column.textContent = addCommaSpace(column.textContent);
+ row.appendChild(column);
+ }
+ tablebody.appendChild(row);
+ }
+
+ table.appendChild(tablehead);
+ table.appendChild(tablebody);
+ div.appendChild(table);
+ console.log(sources);
+}
+
+function init() {
+ fetch("json/sources.json")
+ .then(response => response.json())
+ .then(json => createTable(json));
+}
+
+init();
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java
deleted file mode 100644
index 957b93bd..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/MapWithAIPlugin.java
+++ /dev/null
@@ -1,210 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.swing.JMenuItem;
-
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.actions.PreferencesAction;
-import org.openstreetmap.josm.data.validation.OsmValidator;
-import org.openstreetmap.josm.data.validation.Test;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.MainMenu;
-import org.openstreetmap.josm.gui.MapFrame;
-import org.openstreetmap.josm.gui.download.DownloadDialog;
-import org.openstreetmap.josm.gui.download.DownloadSelection;
-import org.openstreetmap.josm.gui.download.OSMDownloadSource;
-import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.io.remotecontrol.RequestProcessor;
-import org.openstreetmap.josm.plugins.Plugin;
-import org.openstreetmap.josm.plugins.PluginInformation;
-import org.openstreetmap.josm.plugins.mapwithai.backend.DownloadListener;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIAction;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIMoveAction;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIObject;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIRemoteControl;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIUploadHook;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MergeDuplicateWaysAction;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.PreConflatedDataUtils;
-import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.ConnectingNodeInformationTest;
-import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.RoutingIslandsTest;
-import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.StreetAddressOrder;
-import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.StreetAddressTest;
-import org.openstreetmap.josm.plugins.mapwithai.data.validation.tests.StubEndsTest;
-import org.openstreetmap.josm.plugins.mapwithai.gui.MapWithAIMenu;
-import org.openstreetmap.josm.plugins.mapwithai.gui.download.MapWithAIDownloadOptions;
-import org.openstreetmap.josm.plugins.mapwithai.gui.download.MapWithAIDownloadSourceType;
-import org.openstreetmap.josm.plugins.mapwithai.gui.preferences.MapWithAIPreferences;
-import org.openstreetmap.josm.plugins.mapwithai.spi.preferences.MapWithAIConfig;
-import org.openstreetmap.josm.plugins.mapwithai.spi.preferences.MapWithAIUrls;
-import org.openstreetmap.josm.plugins.mapwithai.tools.MapPaintUtils;
-import org.openstreetmap.josm.plugins.mapwithai.tools.MapWithAICopyProhibit;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.tools.Destroyable;
-import org.openstreetmap.josm.tools.Logging;
-
-/**
- * The POJO entry point for the plugin
- */
-public final class MapWithAIPlugin extends Plugin implements Destroyable {
- /** The name of the plugin */
- public static final String NAME = "MapWithAI";
-
- static final String PAINTSTYLE_PREEXISTS = NAME.concat(".paintstyleprexists");
- private static String versionInfo;
-
- private final PreferenceSetting preferenceSetting;
-
- private final List destroyables;
-
- private final MapWithAIMenu mapwithaiMenu;
-
- private static final Map, Boolean> MENU_ENTRIES = new LinkedHashMap<>();
- static {
- MENU_ENTRIES.put(MapWithAIAction.class, false);
- MENU_ENTRIES.put(MapWithAIMoveAction.class, false);
- MENU_ENTRIES.put(MergeDuplicateWaysAction.class, true);
- }
-
- private static final List> VALIDATORS = Arrays.asList(RoutingIslandsTest.class,
- ConnectingNodeInformationTest.class, StubEndsTest.class, StreetAddressTest.class, StreetAddressOrder.class);
-
- public MapWithAIPlugin(PluginInformation info) {
- super(info);
-
- MapWithAIConfig.setUrlsProvider(MapWithAIUrls.getInstance());
-
- preferenceSetting = new MapWithAIPreferences();
-
- // Add MapWithAI specific menu
- final var dataMenu = MainApplication.getMenu().dataMenu;
- mapwithaiMenu = new MapWithAIMenu();
-
- dataMenu.add(mapwithaiMenu);
- for (final Map.Entry, Boolean> entry : MENU_ENTRIES.entrySet()) {
- if (Arrays.stream(mapwithaiMenu.getMenuComponents()).filter(JMenuItem.class::isInstance)
- .map(JMenuItem.class::cast)
- .noneMatch(component -> entry.getKey().equals(component.getAction().getClass()))) {
- try {
- MainMenu.add(mapwithaiMenu, entry.getKey().getDeclaredConstructor().newInstance(),
- entry.getValue());
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException | NoSuchMethodException | SecurityException e) {
- Logging.debug(e);
- }
- }
- }
-
- // Add the preferences last to data
- final var preferenceAction = PreferencesAction.forPreferenceTab(tr("MapWithAI Preferences"),
- tr("MapWithAI Preferences"), MapWithAIPreferences.class);
- MainMenu.add(mapwithaiMenu, preferenceAction);
-
- VALIDATORS.forEach(clazz -> {
- if (!OsmValidator.getAllAvailableTestClasses().contains(clazz)) {
- OsmValidator.addTest(clazz);
- }
- });
-
- if (!Config.getPref().getKeySet().contains(PAINTSTYLE_PREEXISTS)) {
- Config.getPref().putBoolean(PAINTSTYLE_PREEXISTS, MapPaintUtils.checkIfMapWithAIPaintStyleExists());
- }
-
- MapPaintUtils.addMapWithAIPaintStyles();
-
- destroyables = new ArrayList<>();
-
- setVersionInfo(info.localversion);
- RequestProcessor.addRequestHandlerClass("mapwithai", MapWithAIRemoteControl.class);
- new MapWithAIRemoteControl(); // instantiate to get action into Remote Control Preferences
- destroyables.add(new MapWithAIUploadHook(info));
- destroyables.add(new PreConflatedDataUtils());
- mapFrameInitialized(null, MainApplication.getMap());
- OSMDownloadSource.addDownloadType(new MapWithAIDownloadSourceType());
- MainApplication.worker.execute(() -> UpdateProd.doProd(info.mainversion));
- // Preload the MapWithAILayerInfo for the JOSM download window
- // This reduces the amount of time taken for first button click by 100ms.
- // Don't use the worker thread to avoid blocking user downloads
- MapWithAIDataUtils.getForkJoinPool().execute(MapWithAILayerInfo::getInstance);
-
- destroyables.add(new MapWithAICopyProhibit());
- }
-
- @Override
- public void addDownloadSelection(List list) {
- // Run in EDT to avoid blocking (has to be run before MapWithAIDownloadOptions
- // so its already initialized)
- GuiHelper.runInEDT(MapWithAILayerInfo::getInstance);
- final var mapWithAIDownloadOptions = new MapWithAIDownloadOptions();
- MainApplication.worker
- .execute(() -> GuiHelper.runInEDT(() -> mapWithAIDownloadOptions.addGui(DownloadDialog.getInstance())));
- destroyables.add(mapWithAIDownloadOptions);
- }
-
- @Override
- public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
- final var mapWithAIObject = destroyables.stream().filter(MapWithAIObject.class::isInstance)
- .map(MapWithAIObject.class::cast).findFirst().orElseGet(MapWithAIObject::new);
- if ((oldFrame != null) && (oldFrame.statusLine != null)) {
- mapWithAIObject.removeMapStatus(oldFrame.statusLine);
- }
- if ((newFrame != null) && (newFrame.statusLine != null)) {
- mapWithAIObject.addMapStatus(newFrame.statusLine);
- }
- if (!destroyables.contains(mapWithAIObject)) {
- destroyables.add(mapWithAIObject);
- }
- }
-
- @Override
- public PreferenceSetting getPreferenceSetting() {
- return preferenceSetting;
- }
-
- /**
- * The current version for the plugin
- *
- * @return The version information of the plugin
- */
- public static String getVersionInfo() {
- return versionInfo;
- }
-
- private static void setVersionInfo(String newVersionInfo) {
- versionInfo = newVersionInfo;
- }
-
- /**
- * This is so that if JOSM ever decides to support updating plugins without
- * restarting, I don't have to do anything (hopefully -- I might have to change
- * the interface and method). Not currently used... (October 16, 2019)
- */
- @Override
- public void destroy() {
- MainApplication.getMenu().dataMenu.remove(this.mapwithaiMenu);
-
- MainApplication.getLayerManager().getLayersOfType(MapWithAILayer.class)
- .forEach(layer -> MainApplication.getLayerManager().removeLayer(layer));
-
- if (!Config.getPref().getBoolean(PAINTSTYLE_PREEXISTS)) {
- MapPaintUtils.removeMapWithAIPaintStyles();
- }
-
- destroyables.forEach(Destroyable::destroy);
- OSMDownloadSource.removeDownloadType(OSMDownloadSource.getDownloadType(MapWithAIDownloadSourceType.class));
- VALIDATORS.forEach(OsmValidator::removeTest);
- DownloadListener.destroyAll();
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/UpdateProd.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/UpdateProd.java
deleted file mode 100644
index 73a6fd50..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/UpdateProd.java
+++ /dev/null
@@ -1,49 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import javax.swing.JOptionPane;
-
-import org.openstreetmap.josm.data.Version;
-import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.tools.OpenBrowser;
-
-/**
- * Prod users to update JOSM (only increment to new compile version)
- *
- * @author Taylor Smock
- */
-public final class UpdateProd {
- private UpdateProd() {
- // Hide constructor
- }
-
- /**
- * Prod user to update JOSM
- *
- * @param nextMinVersion The next version of JOSM that is supported by MapWithAI
- * @return {@code true} if an update is needed
- */
- public static boolean doProd(int nextMinVersion) {
- if (nextMinVersion > Version.getInstance().getVersion()
- && Version.getInstance().getVersion() != Version.JOSM_UNKNOWN_VERSION) {
- int doUpdate = ConditionalOptionPaneUtil.showOptionDialog(
- MapWithAIPlugin.NAME.concat(".ignore_next_version"), MainApplication.getMainFrame(),
- tr("Please update JOSM -- {0} {1} is the last {0} version to support JOSM {2}",
- MapWithAIPlugin.NAME, MapWithAIPlugin.getVersionInfo(),
- Integer.toString(Version.getInstance().getVersion())),
- tr("{0}: Please update JOSM", MapWithAIPlugin.NAME), JOptionPane.YES_NO_OPTION,
- JOptionPane.INFORMATION_MESSAGE, new Object[] { tr("Update"), tr("Don''t Update") }, tr("Update"));
- if (doUpdate == 0) {
- OpenBrowser.displayUrl("https://josm.openstreetmap.de");
- }
- return true;
- }
- Config.getPref().put(MapWithAIPlugin.NAME.concat(".ignore_next_version"), null);
- Config.getPref().put(MapWithAIPlugin.NAME.concat(".ignore_next_version.value"), null);
- return false;
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/actions/AddMapWithAILayerAction.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/actions/AddMapWithAILayerAction.java
deleted file mode 100644
index 40c2c204..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/actions/AddMapWithAILayerAction.java
+++ /dev/null
@@ -1,152 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.actions;
-
-import static java.util.function.Predicate.not;
-import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
-
-import java.awt.event.ActionEvent;
-import java.io.Serial;
-import java.util.ArrayList;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ForkJoinTask;
-
-import org.openstreetmap.josm.actions.AdaptableAction;
-import org.openstreetmap.josm.actions.AddImageryLayerAction;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmData;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
-import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
-import org.openstreetmap.josm.tools.ImageResource;
-import org.openstreetmap.josm.tools.JosmRuntimeException;
-import org.openstreetmap.josm.tools.Logging;
-
-/**
- * Action displayed in MapWithAI menu to add data to the MapWithAI layer.
- * Largely copied from {@link AddImageryLayerAction}.
- */
-public class AddMapWithAILayerAction extends JosmAction implements AdaptableAction {
- @Serial
- private static final long serialVersionUID = 1403912860658467920L;
- private final transient MapWithAIInfo info;
-
- /**
- * Constructs a new {@code AddMapWithAILayerAction} for the given
- * {@code MapWithAIInfo}. If an http:// icon is specified, it is fetched
- * asynchronously.
- *
- * @param info The source info
- */
- public AddMapWithAILayerAction(MapWithAIInfo info) {
- super(info.getName(), /* ICON */"imagery_menu", info.getToolTipText(), null, true,
- ToolbarPreferences.IMAGERY_PREFIX + info.getToolbarName(), false);
- setHelpId(ht("/Preferences/Imagery"));
- this.info = info;
- installAdapters();
-
- // change toolbar icon from if specified
- final var icon = info.getIcon();
- if (icon != null) {
- final var future = new ImageProvider(icon).setOptional(true).getResourceAsync(result -> {
- if (result != null) {
- GuiHelper.runInEDT(() -> result.attachImageIcon(this));
- }
- });
- try {
- future.get();
- } catch (InterruptedException e) {
- Logging.error(e);
- Thread.currentThread().interrupt();
- } catch (ExecutionException e) {
- Logging.error(e);
- }
- } else {
- try {
- final var resource = new ImageResource(
- this.info.getSourceCategory().getIcon(ImageSizes.MENU).getImage());
- resource.attachImageIcon(this);
- } catch (JosmRuntimeException e) {
- // Eclipse doesn't like giving applications their resources...
- if (!e.getMessage().contains("failed to locate image")) {
- throw e;
- }
- }
- }
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- if (isEnabled()) {
- MainApplication.worker.execute(() -> realRun(this.info));
- }
- }
-
- /**
- * Run the download tasks. This should be run off of the EDT, see #23529.
- *
- * @param info The external data to download
- */
- private static void realRun(MapWithAIInfo info) {
- MapWithAILayer layer = MapWithAIDataUtils.getLayer(false);
- final DataSet ds;
- final OsmData, ?, ?, ?> boundsSource;
- final var dataLayer = getDataLayer();
- if (layer != null && !layer.getData().getDataSourceBounds().isEmpty()) {
- ds = layer.getDataSet();
- boundsSource = ds;
- } else if (dataLayer != null && !dataLayer.getDataSet().getDataSourceBounds().isEmpty()) {
- boundsSource = dataLayer.getDataSet();
- layer = MapWithAIDataUtils.getLayer(true);
- ds = layer.getDataSet();
- } else {
- boundsSource = null;
- ds = null;
- }
- if (boundsSource != null && ds != null) {
- final var pool = MapWithAIDataUtils.getForkJoinPool();
- final var forkJoinTasks = new ArrayList>(boundsSource.getDataSourceBounds().size());
- for (var b : boundsSource.getDataSourceBounds()) {
- final var task = MapWithAIDataUtils.download(NullProgressMonitor.INSTANCE, b, info,
- MapWithAIDataUtils.MAXIMUM_SIDE_DIMENSIONS);
- forkJoinTasks.add(task);
- pool.execute(task);
- }
- for (var task : forkJoinTasks) {
- ds.mergeFrom(task.join());
- }
- }
- if (layer != null) {
- layer.addDownloadedInfo(info);
- }
- }
-
- private static OsmDataLayer getDataLayer() {
- return MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
- .filter(not(MapWithAILayer.class::isInstance)).findFirst().orElse(null);
- }
-
- @Override
- protected boolean listenToSelectionChange() {
- return false;
- }
-
- @Override
- protected void updateEnabledState() {
- setEnabled(!info.isBlacklisted() && MainApplication.getLayerManager().getActiveDataLayer() != null
- && (MapWithAIDataUtils.getLayer(false) == null
- || !MapWithAIDataUtils.getLayer(false).hasDownloaded(info)));
- }
-
- @Override
- public String toString() {
- return "AddMapWithAILayerAction [info=" + info + ']';
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/BoundingBoxMapWithAIDownloader.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/BoundingBoxMapWithAIDownloader.java
deleted file mode 100644
index 2b55e1aa..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/BoundingBoxMapWithAIDownloader.java
+++ /dev/null
@@ -1,522 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import javax.swing.JOptionPane;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UncheckedIOException;
-import java.net.MalformedURLException;
-import java.net.SocketTimeoutException;
-import java.net.URI;
-import java.net.URL;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.nio.charset.StandardCharsets;
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.DataSource;
-import org.openstreetmap.josm.data.imagery.ImageryInfo;
-import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MVTTile;
-import org.openstreetmap.josm.data.imagery.vectortile.mapbox.MapboxVectorTileSource;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.IPrimitive;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveId;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.RelationMember;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.vector.VectorNode;
-import org.openstreetmap.josm.data.vector.VectorPrimitive;
-import org.openstreetmap.josm.data.vector.VectorRelation;
-import org.openstreetmap.josm.data.vector.VectorRelationMember;
-import org.openstreetmap.josm.data.vector.VectorWay;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.io.BoundingBoxDownloader;
-import org.openstreetmap.josm.io.GeoJSONReader;
-import org.openstreetmap.josm.io.IllegalDataException;
-import org.openstreetmap.josm.io.OsmApiException;
-import org.openstreetmap.josm.io.OsmReader;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIConflationCategory;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIType;
-import org.openstreetmap.josm.plugins.mapwithai.tools.MapPaintUtils;
-import org.openstreetmap.josm.plugins.pmtiles.data.imagery.PMTilesImageryInfo;
-import org.openstreetmap.josm.plugins.pmtiles.gui.layers.PMTilesImageSource;
-import org.openstreetmap.josm.plugins.pmtiles.lib.DirectoryCache;
-import org.openstreetmap.josm.plugins.pmtiles.lib.Header;
-import org.openstreetmap.josm.plugins.pmtiles.lib.PMTiles;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.tools.HttpClient;
-import org.openstreetmap.josm.tools.JosmRuntimeException;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-
-import jakarta.json.Json;
-import jakarta.json.JsonValue;
-import jakarta.json.stream.JsonParser;
-
-/**
- * A bounding box downloader for MapWithAI
- *
- * @author Taylor Smock
- */
-public class BoundingBoxMapWithAIDownloader extends BoundingBoxDownloader {
- private final String url;
- private final boolean crop;
- private final int start;
-
- private static long lastErrorTime;
-
- private final Bounds downloadArea;
- private final MapWithAIInfo info;
- private DataConflationSender dcs;
-
- private static final int DEFAULT_TIMEOUT = 50_000; // 50 seconds
-
- /**
- * Create a new {@link BoundingBoxMapWithAIDownloader} object
- *
- * @param downloadArea The area to download
- * @param info The info to use to get the url to download
- * @param crop Whether or not to crop the download area
- */
- public BoundingBoxMapWithAIDownloader(Bounds downloadArea, MapWithAIInfo info, boolean crop) {
- this(downloadArea, info, crop, 0);
- }
-
- /**
- * Create a new {@link BoundingBoxMapWithAIDownloader} object
- *
- * @param downloadArea The area to download
- * @param info The info to use to get the url to download
- * @param crop Whether or not to crop the download area
- * @param start The number of objects to skip (Esri only please)
- */
- private BoundingBoxMapWithAIDownloader(Bounds downloadArea, MapWithAIInfo info, boolean crop, int start) {
- super(downloadArea);
- this.info = info;
- this.url = info.getUrlExpanded();
- this.crop = crop;
- this.downloadArea = downloadArea;
- this.start = start;
- }
-
- @Override
- protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) {
- if (url.contains("{x}") && url.contains("{y}") && url.contains("{z}")) {
- final var tile = TileXYZ.tileFromBBox(lon1, lat1, lon2, lat2);
- return getRequestForTile(tile);
- }
- var current = url.replace("{bbox}", Double.toString(lon1) + ',' + lat1 + ',' + lon2 + ',' + lat2)
- .replace("{xmin}", Double.toString(lon1)).replace("{ymin}", Double.toString(lat1))
- .replace("{xmax}", Double.toString(lon2)).replace("{ymax}", Double.toString(lat2));
- boolean hasQuery = !Optional.ofNullable(URI.create(current).getRawQuery()).map(String::isEmpty).orElse(true);
-
- if (crop) {
- current += (hasQuery ? '&' : '?') + "crop_bbox="
- + DetectTaskingManagerUtils.getTaskingManagerBounds().toBBox().toStringCSV(",");
- hasQuery = true;
- }
- if (this.info.getSourceType() == MapWithAIType.ESRI_FEATURE_SERVER && !this.info.isConflated()) {
- current += (hasQuery ? '&' : '?') + "resultOffset=" + this.start;
- }
- return current;
- }
-
- private String getRequestForTile(TileXYZ tile) {
- return url.replace("{x}", Long.toString(tile.x())).replace("{y}", Long.toString(tile.y())).replace("{z}",
- Long.toString(tile.z()));
- }
-
- @Override
- public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
- long startTime = System.nanoTime();
- try {
- var externalData = super.parseOsm(progressMonitor);
- // Don't call conflate code unnecessarily
- if ((this.info.getSourceType() != MapWithAIType.ESRI_FEATURE_SERVER || this.start == 0)
- && Boolean.TRUE.equals(MapWithAIInfo.THIRD_PARTY_CONFLATE.get()) && !this.info.isConflated()
- && !MapWithAIConflationCategory.conflationUrlFor(this.info.getCategory()).isEmpty()) {
- if (externalData.getDataSourceBounds().isEmpty()) {
- externalData.addDataSource(new DataSource(this.downloadArea, "External Data"));
- }
- final var toConflate = getConflationData(this.downloadArea);
- dcs = new DataConflationSender(this.info.getCategory(), toConflate, externalData);
- dcs.run();
- try {
- final var conflatedData = dcs.get(30, TimeUnit.SECONDS);
- if (conflatedData != null) {
- externalData = conflatedData;
- }
- } catch (InterruptedException e) {
- Logging.error(e);
- Thread.currentThread().interrupt();
- } catch (ExecutionException | TimeoutException e) {
- Logging.error(e);
- }
- }
- MapPaintUtils.addSourcesToPaintStyle(externalData);
- return externalData;
- } catch (OsmApiException e) {
- if (!(e.getResponseCode() == 504 && (System.nanoTime() - lastErrorTime) < 120_000_000_000L)) {
- throw e;
- }
- } catch (OsmTransferException e) {
- if (e.getCause() instanceof SocketTimeoutException && (System.nanoTime() - startTime) > 30_000_000_000L) {
- updateLastErrorTime(System.nanoTime());
- final var note = new Notification();
- GuiHelper.runInEDT(() -> note.setContent(tr(
- "Attempting to download data in the background. This may fail or succeed in a few minutes.")));
- GuiHelper.runInEDT(note::show);
- } else if (e.getCause() instanceof IllegalDataException) {
- final Instant lastUpdated;
- final var now = Instant.now();
- synchronized (BoundingBoxMapWithAIDownloader.class) {
- lastUpdated = Instant.ofEpochSecond(Config.getPref().getLong("mapwithai.layerinfo.lastupdated", 0));
- Config.getPref().putLong("mapwithai.layerinfo.lastupdated", now.getEpochSecond());
- }
- // Only force an update if the last update time is sufficiently old.
- if (now.toEpochMilli() - lastUpdated.toEpochMilli() > TimeUnit.MINUTES.toMillis(10)) {
- MapWithAILayerInfo.getInstance().loadDefaults(true, MapWithAIDataUtils.getForkJoinPool(), false,
- () -> GuiHelper.runInEDT(() -> {
- final var notification = new Notification(tr(
- "MapWithAI layers reloaded. Removing and re-adding the MapWithAI layer may be necessary."));
- notification.setIcon(JOptionPane.INFORMATION_MESSAGE);
- notification.setDuration(Notification.TIME_LONG);
- notification.show();
- }));
- }
- } else {
- throw e;
- }
- }
- // Just in case something happens, try again...
- final var ds = new DataSet();
- final var runnable = new GetDataRunnable(downloadArea, ds, NullProgressMonitor.INSTANCE);
- runnable.setMapWithAIInfo(info);
- MainApplication.worker.execute(() -> {
- try {
- // It seems that the server has issues if I make a request soon
- // after the failing request due to a timeout.
- TimeUnit.SECONDS.sleep(10);
- } catch (InterruptedException e1) {
- Thread.currentThread().interrupt();
- }
- runnable.compute();
- });
-
- MapPaintUtils.addSourcesToPaintStyle(ds);
- return ds;
- }
-
- /**
- * Get data to send to the conflation server
- *
- * @param bound The bounds that we are sending to the server
- * @return The dataset to send to the server
- */
- private static DataSet getConflationData(Bounds bound) {
- final var area = DataSource.getDataSourceArea(Collections.singleton(new DataSource(bound, "")));
- if (area != null) {
- final var layers = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream().filter(
- l -> l.getDataSet().getDataSourceBounds().stream().anyMatch(b -> area.contains(bound.asRect())))
- .toList();
- return layers.stream().max(Comparator.comparingInt(l -> l.getDataSet().allPrimitives().size()))
- .map(OsmDataLayer::getDataSet).orElse(null);
- }
- return null;
- }
-
- private static void updateLastErrorTime(long time) {
- lastErrorTime = time;
- }
-
- @Override
- protected DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
- DataSet ds;
- final var contentType = this.activeConnection.getResponse().getContentType();
- if (this.info.getSourceType() == MapWithAIType.PMTILES
- || this.info.getSourceType() == MapWithAIType.MAPBOX_VECTOR_TILE) {
- ds = readMvt(source, progressMonitor);
- } else if (Arrays.asList("text/json", "application/json", "application/geo+json").contains(contentType)
- // Fall back to Esri Feature Server check. They don't always indicate a json
- // return type. :(
- || (this.info.getSourceType() == MapWithAIType.ESRI_FEATURE_SERVER && !this.info.isConflated())) {
- ds = readJson(source, progressMonitor);
- } else {
- // Fall back to XML parsing
- ds = OsmReader.parseDataSet(source, progressMonitor, OsmReader.Options.CONVERT_UNKNOWN_TO_TAGS,
- OsmReader.Options.SAVE_ORIGINAL_ID);
- }
- if (url != null && info.getUrl() != null && !info.getUrl().trim().isEmpty()) {
- if (info.getSource() != null) {
- GetDataRunnable.addSourceTag(ds, info.getSource());
- } else {
- GetDataRunnable.addMapWithAISourceTag(ds, getMapWithAISourceTag(info));
- }
- }
- GetDataRunnable.cleanup(ds, downloadArea, info);
- return ds;
- }
-
- private DataSet readMvt(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
- final DataSet ds;
- final TileSource tileSource;
- final List tiles;
- final Header header;
- final DirectoryCache cachedDirectories;
- if (this.info.getSourceType() == MapWithAIType.PMTILES) {
- try {
- header = PMTiles.readHeader(URI.create(this.url));
- final var root = PMTiles.readRootDirectory(header);
- tiles = TileXYZ.tilesFromBBox(header.maxZoom(), this.downloadArea).toList();
- cachedDirectories = new DirectoryCache(root);
- tileSource = new PMTilesImageSource(new PMTilesImageryInfo(header));
- } catch (IOException e) {
- throw new IllegalDataException(e);
- }
- } else {
- header = null;
- cachedDirectories = null;
- // Assume the source is added by the user
- final int zoom;
- if (this.info.getMaxZoom() == 0) {
- var z = 18;
- for (; z > 0; z--) {
- final var tileOptional = TileXYZ.tilesFromBBox(z, this.downloadArea).findFirst();
- if (tileOptional.isPresent()) {
- final var tile = tileOptional.get();
- try {
- final var responseHeader = java.net.http.HttpClient.newBuilder()
- .followRedirects(java.net.http.HttpClient.Redirect.NORMAL).build()
- .send(HttpRequest.newBuilder().uri(URI.create(this.getRequestForTile(tile)))
- .method("HEAD", HttpRequest.BodyPublishers.noBody()).build(),
- HttpResponse.BodyHandlers.discarding());
- if (responseHeader.statusCode() >= 200 && responseHeader.statusCode() < 300) {
- break;
- }
- } catch (IOException e) {
- Logging.trace(e);
- } catch (InterruptedException e) {
- Logging.trace(e);
- Thread.currentThread().interrupt();
- return null;
- }
- }
- }
- zoom = z;
- } else {
- zoom = this.info.getMaxZoom();
- }
- tiles = TileXYZ.tilesFromBBox(zoom, this.downloadArea).toList();
- tileSource = new MapboxVectorTileSource(new ImageryInfo(this.url, this.url));
- }
- ds = new DataSet();
- final var currentBounds = new Bounds(this.downloadArea);
- progressMonitor.beginTask(tr("Downloading data"), 2 * tiles.size());
- for (TileXYZ tileXYZ : tiles) {
- try {
- final var hilbert = PMTiles.convertToHilbert(tileXYZ.z(), tileXYZ.x(), tileXYZ.y());
- final var data = this.info.getSourceType() == MapWithAIType.PMTILES
- ? new ByteArrayInputStream(PMTiles.readData(header, hilbert, cachedDirectories))
- : getInputStream(getRequestForTile(tileXYZ), progressMonitor.createSubTaskMonitor(1, true));
- final var dataSet = loadTile(tileSource, tileXYZ, data);
- ds.mergeFrom(dataSet, progressMonitor.createSubTaskMonitor(1, true));
- tileXYZ.expandBounds(currentBounds);
- } catch (OsmTransferException | IOException e) {
- progressMonitor.finishTask();
- throw new IllegalDataException(e);
- }
- }
- progressMonitor.finishTask();
- ds.addDataSource(new DataSource(currentBounds, this.url));
- return ds;
- }
-
- private static DataSet loadTile(TileSource tileSource, TileXYZ tileXYZ, InputStream actualSource)
- throws IllegalDataException {
-
- final var tile = new MVTTile(tileSource, tileXYZ.x(), tileXYZ.y(), tileXYZ.z());
- try {
- tile.loadImage(actualSource);
- } catch (IOException e) {
- throw new IllegalDataException(e);
- }
- final var ds = new DataSet();
- final var primitiveMap = new HashMap(tile.getData().getAllPrimitives().size());
- for (Class extends VectorPrimitive> clazz : Arrays.asList(VectorNode.class, VectorWay.class,
- VectorRelation.class)) {
- for (VectorPrimitive p : Utils.filteredCollection(tile.getData().getAllPrimitives(), clazz)) {
- final OsmPrimitive osmPrimitive;
- if (p instanceof VectorNode node) {
- osmPrimitive = new Node(node.getCoor());
- osmPrimitive.putAll(node.getKeys());
- } else if (p instanceof VectorWay way) {
- final var tWay = new Way();
- for (VectorNode node : way.getNodes()) {
- tWay.addNode((Node) primitiveMap.get(node));
- }
- tWay.putAll(way.getKeys());
- osmPrimitive = tWay;
- } else if (p instanceof VectorRelation vectorRelation) {
- final var tRelation = new Relation();
- for (VectorRelationMember member : vectorRelation.getMembers()) {
- tRelation.addMember(new RelationMember(member.getRole(), primitiveMap.get(member.getMember())));
- }
- tRelation.putAll(vectorRelation.getKeys());
- osmPrimitive = tRelation;
- } else {
- throw new IllegalDataException("Unknown vector data type: " + p);
- }
- ds.addPrimitive(osmPrimitive);
- primitiveMap.put(p, osmPrimitive);
- }
- }
- return ds;
- }
-
- private DataSet readJson(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
- final DataSet ds;
- // Rather unfortunately, we need to read the entire json in order to figure out
- // if we need to make additional calls
- try (var reader = Json.createReader(source)) {
- final var structure = reader.read();
- try (var bais = new ByteArrayInputStream(structure.toString().getBytes(StandardCharsets.UTF_8))) {
- ds = GeoJSONReader.parseDataSet(bais, progressMonitor);
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- /* We should only call this from the "root" call */
- if (this.start == 0 && structure.getValueType() == JsonValue.ValueType.OBJECT) {
- final var serverObj = structure.asJsonObject();
- final boolean exceededTransferLimit = serverObj.entrySet().stream()
- .filter(entry -> "properties".equals(entry.getKey())
- && entry.getValue().getValueType() == JsonValue.ValueType.OBJECT)
- .map(Map.Entry::getValue).map(JsonValue::asJsonObject)
- .map(obj -> obj.getBoolean("exceededTransferLimit", false)).findFirst().orElse(false);
- if (exceededTransferLimit && this.info.getSourceType() == MapWithAIType.ESRI_FEATURE_SERVER) {
- final int size = serverObj.getJsonArray("features").size();
- final var other = this.getAdditionalEsriData(progressMonitor,
- this.getRequestForBbox(this.lon1, this.lat1, this.lon2, this.lat2), size);
- ds.mergeFrom(other, progressMonitor.createSubTaskMonitor(0, false));
- }
- }
- }
- if (info.getReplacementTags() != null) {
- GetDataRunnable.replaceKeys(ds, info.getReplacementTags());
- }
- return ds;
- }
-
- private DataSet getAdditionalEsriData(ProgressMonitor progressMonitor, String baseUrl, int size) {
- final var returnDs = new DataSet();
- try {
- final var client = HttpClient.create(new URL(baseUrl + "&returnCountOnly=true"));
- int objects = Integer.MIN_VALUE;
- try (var is = client.connect().getContent(); var parser = Json.createParser(is)) {
- while (parser.hasNext()) {
- final var event = parser.next();
- if (event == JsonParser.Event.START_OBJECT) {
- final var objCount = parser.getObjectStream()
- .filter(entry -> "properties".equals(entry.getKey()))
- .map(entry -> entry.getValue().asJsonObject())
- .mapToInt(properties -> properties.getInt("count")).findFirst();
- if (objCount.isPresent()) {
- objects = objCount.getAsInt();
- break;
- }
- }
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- } finally {
- client.disconnect();
- }
- // Zero indexed. Esri uses 2000 as the limit. 0-1999 is 2000 objects, so we want
- // to start at 2000 for the next round.
- try {
- progressMonitor.beginTask(tr("Downloading additional data"), objects);
- // We have already downloaded some of the objects. Set the ticks.
- progressMonitor.worked(size);
- while (progressMonitor.getTicks() < progressMonitor.getTicksCount() - 1) {
- final var next = new BoundingBoxMapWithAIDownloader(this.downloadArea, this.info, this.crop,
- this.start + size).parseOsm(progressMonitor.createSubTaskMonitor(0, false));
- progressMonitor.worked((int) next.allPrimitives().stream().filter(IPrimitive::isTagged).count());
- returnDs.mergeFrom(next);
- }
- } catch (OsmTransferException e) {
- throw new JosmRuntimeException(e);
- } finally {
- progressMonitor.finishTask();
- }
- } catch (MalformedURLException e) {
- throw new UncheckedIOException(e);
- }
- return returnDs;
- }
-
- private static String getMapWithAISourceTag(MapWithAIInfo info) {
- return info.getName() == null ? MapWithAIPlugin.NAME : info.getName();
- }
-
- /**
- * Returns the name of the download task to be displayed in the
- * {@link ProgressMonitor}.
- *
- * @return task name
- */
- @Override
- protected String getTaskName() {
- return tr("Contacting {0} Server...", MapWithAIPlugin.NAME);
- }
-
- @Override
- protected String getBaseUrl() {
- return url;
- }
-
- @Override
- protected void adaptRequest(HttpClient request) {
- final var defaultUserAgent = new StringBuilder();
- request.setReadTimeout(DEFAULT_TIMEOUT);
- defaultUserAgent.append(request.getHeaders().get("User-Agent"));
- if (defaultUserAgent.toString().trim().length() == 0) {
- defaultUserAgent.append("JOSM");
- }
- defaultUserAgent.append(tr("/ {0} {1}", MapWithAIPlugin.NAME, MapWithAIPlugin.getVersionInfo()));
- request.setHeader("User-Agent", defaultUserAgent.toString());
- }
-
- @Override
- public void cancel() {
- super.cancel();
- if (dcs != null) {
- dcs.cancel(true);
- }
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataAvailability.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataAvailability.java
deleted file mode 100644
index 57ceb682..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataAvailability.java
+++ /dev/null
@@ -1,276 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.io.CachedFile;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
-import org.openstreetmap.josm.plugins.mapwithai.spi.preferences.MapWithAIConfig;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Territories;
-import org.openstreetmap.josm.tools.Utils;
-
-import jakarta.json.Json;
-import jakarta.json.JsonException;
-import jakarta.json.JsonObject;
-import jakarta.json.JsonValue;
-
-/**
- * Check for data availability in an area
- */
-public class DataAvailability {
- /** A map of tag -> message of possible data types */
- static final Map POSSIBLE_DATA_POINTS = new TreeMap<>();
-
- private static final String PROVIDES = "provides";
- private static final int SEVEN_DAYS_IN_SECONDS = 604_800;
-
- /**
- * Map<Source,
- * Map<(url|parameters|countries|license|osm_compatible|permission_url),
- * Object>>
- */
- protected static final Map> COUNTRY_MAP = new HashMap<>();
- /**
- * This holds classes that can give availability of data for a specific service
- */
- private static final List> DATA_SOURCES = new ArrayList<>();
-
- private static DataAvailability instance;
-
- /**
- * A map of countries to a map of available types
- * ({@code Map>}
- */
- static final Map> COUNTRIES = new HashMap<>();
-
- protected DataAvailability() {
- if (DataAvailability.class.equals(this.getClass())) {
- initialize();
- }
- }
-
- /**
- * Initialize the class
- */
- private static void initialize() {
- try (var jsonFile = new CachedFile(MapWithAIConfig.getUrls().getMapWithAISourcesJson());
- var jsonParser = Json.createParser(jsonFile.getContentReader())) {
- jsonFile.setMaxAge(SEVEN_DAYS_IN_SECONDS);
- jsonParser.next();
- final var jsonObject = jsonParser.getObject();
- for (var entry : jsonObject.entrySet()) {
- Logging.debug("{0}: {1}", entry.getKey(), entry.getValue());
- if (JsonValue.ValueType.OBJECT == entry.getValue().getValueType()
- && entry.getValue().asJsonObject().containsKey("countries")) {
- final var countries = entry.getValue().asJsonObject().get("countries");
- parseCountries(COUNTRIES, countries, entry.getValue());
- }
- }
- } catch (JsonException | IOException e) {
- Logging.debug(e);
- }
-
- DATA_SOURCES.forEach(clazz -> {
- try {
- clazz.getConstructor().newInstance();
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException | NoSuchMethodException | SecurityException e) {
- Logging.debug(e);
- }
- });
- }
-
- /**
- * Parse a JSON Value for country information
- *
- * @param countriesMap The countries map (will be modified)
- * @param countries The country object (JsonObject)
- * @param information The information for the source
- */
- private static void parseCountries(Map> countriesMap, JsonValue countries,
- JsonValue information) {
- if (JsonValue.ValueType.OBJECT == countries.getValueType()) {
- parseCountriesObject(countriesMap, countries.asJsonObject(), information);
- } else {
- Logging.error("MapWithAI: Check format of countries map from MapWithAI");
- }
- }
-
- /**
- * Parse a JsonObject for countries
- *
- * @param countriesMap The countries map (will be modified)
- * @param countryObject The country object (JsonObject)
- * @param information The information for the source
- */
- private static void parseCountriesObject(Map> countriesMap, JsonObject countryObject,
- JsonValue information) {
- for (Map.Entry entry : countryObject.entrySet()) {
- Map providesMap = countriesMap.getOrDefault(entry.getKey(), new TreeMap<>());
- countriesMap.putIfAbsent(entry.getKey(), providesMap);
- if (JsonValue.ValueType.ARRAY == entry.getValue().getValueType()) {
- for (String provide : entry.getValue().asJsonArray().stream()
- .filter(c -> JsonValue.ValueType.STRING == c.getValueType()).map(JsonValue::toString)
- .map(DataAvailability::stripQuotes).toList()) {
- providesMap.put(provide, true);
- }
- }
- if (providesMap.isEmpty() && JsonValue.ValueType.OBJECT == information.getValueType()
- && information.asJsonObject().containsKey(PROVIDES)
- && JsonValue.ValueType.ARRAY == information.asJsonObject().get(PROVIDES).getValueType()) {
- for (String provide : information.asJsonObject().getJsonArray(PROVIDES).stream()
- .filter(val -> JsonValue.ValueType.STRING == val.getValueType()).map(JsonValue::toString)
- .map(DataAvailability::stripQuotes).toList()) {
- providesMap.put(provide, Boolean.TRUE);
- }
- }
- }
- }
-
- /**
- * Strip double quotes (") from a string
- *
- * @param string A string that may have quotes at the beginning, the end, or
- * both
- * @return A string that doesn't have quotes at the beginning or end
- */
- public static String stripQuotes(String string) {
- return string.replaceAll("((^\")|(\"$))", "");
- }
-
- /**
- * Get the global instance that should be used to check for data availability
- *
- * @return the unique instance
- */
- public static DataAvailability getInstance() {
- if (instance == null || COUNTRIES.isEmpty() || MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty()) {
- instance = new DataAvailability();
- }
- return instance;
- }
-
- /**
- * Check if a bounds may have data
- *
- * @param bounds An area that may have data
- * @return True if one of the corners of the {@code bounds} is in a country with
- * available data.
- */
- public boolean hasData(Bounds bounds) {
- final List corners = new ArrayList<>();
- corners.add(bounds.getMin());
- corners.add(new LatLon(bounds.getMinLat(), bounds.getMaxLon()));
- corners.add(bounds.getMax());
- corners.add(new LatLon(bounds.getMaxLat(), bounds.getMinLon()));
- corners.add(bounds.getCenter());
- return corners.stream().anyMatch(this::hasData);
- }
-
- /**
- * Check if a latlon point may have data
- *
- * @param latLon A point that may have data from MapWithAI
- * @return true if it is in an ares with data from MapWithAI
- */
- public boolean hasData(LatLon latLon) {
- for (final Map.Entry> entry : COUNTRIES.entrySet()) {
- Logging.debug(entry.getKey());
- if (Territories.isIso3166Code(entry.getKey(), latLon)) {
- return entry.getValue().entrySet().stream().anyMatch(Map.Entry::getValue);
- }
- }
- return false;
- }
-
- /**
- * Get data types that may be visible around a point
- *
- * @param latLon The point of interest
- * @return A map that may have available data types (or be empty)
- */
- public static Map getDataTypes(LatLon latLon) {
- return COUNTRIES.entrySet().stream().filter(entry -> Territories.isIso3166Code(entry.getKey(), latLon))
- .map(Map.Entry::getValue).findFirst().orElse(Collections.emptyMap());
- }
-
- /**
- * Get the URL that this class is responsible for
- *
- * @return The url (e.g., example.com/addresses/{bbox}), or null if generic
- */
- public String getUrl() {
- return null;
- }
-
- /**
- * Get the Terms Of Use Url
- *
- * @return The url or ""
- */
- public String getTermsOfUseUrl() {
- return "";
- }
-
- /**
- * Get the Privacy Policy Url
- *
- * @return The url or the TOS url (sometimes a privacy policy is in the TOS).
- * The TOS url may be an empty string.
- */
- public String getPrivacyPolicyUrl() {
- return getTermsOfUseUrl();
- }
-
- /**
- * Get the terms of use for all specially handled URL's
- *
- * @return List of terms of use urls
- */
- public static List getTermsOfUse() {
- return Stream.concat(MapWithAILayerInfo.getInstance().getLayers().stream().map(MapWithAIInfo::getTermsOfUseURL),
- DATA_SOURCES.stream().map(clazz -> {
- try {
- return clazz.getConstructor().newInstance().getTermsOfUseUrl();
- } catch (ReflectiveOperationException e) {
- Logging.debug(e);
- }
- return "";
- })).filter(Objects::nonNull).filter(str -> !Utils.removeWhiteSpaces(str).isEmpty())
- .collect(Collectors.toList());
- }
-
- /**
- * Get the privacy policy for all specially handled URL's
- *
- * @return List of privacy policy urls
- */
- public static List getPrivacyPolicy() {
- return Stream
- .concat(MapWithAILayerInfo.getInstance().getLayers().stream().map(MapWithAIInfo::getPrivacyPolicyURL),
- DATA_SOURCES.stream().map(clazz -> {
- try {
- return clazz.getConstructor().newInstance().getPrivacyPolicyUrl();
- } catch (ReflectiveOperationException e) {
- Logging.debug(e);
- }
- return "";
- }))
- .filter(Objects::nonNull).filter(str -> !Utils.removeWhiteSpaces(str).isEmpty()).distinct()
- .collect(Collectors.toList());
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSender.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSender.java
deleted file mode 100644
index 0ac424e5..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DataConflationSender.java
+++ /dev/null
@@ -1,186 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.net.URI;
-import java.net.URLEncoder;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.net.http.HttpTimeoutException;
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-import java.util.Map;
-import java.util.Objects;
-import java.util.TreeMap;
-import java.util.UUID;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.RunnableFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.openstreetmap.josm.io.IllegalDataException;
-import org.openstreetmap.josm.io.NetworkManager;
-import org.openstreetmap.josm.io.OsmReader;
-import org.openstreetmap.josm.io.OsmWriterFactory;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAICategory;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIConflationCategory;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * Conflate data with a third party server
- *
- * @author Taylor Smock
- */
-public class DataConflationSender implements RunnableFuture {
-
- private static final int MAX_POLLS = 100;
- private final DataSet external;
- private final DataSet osm;
- private final MapWithAICategory category;
- private DataSet conflatedData;
- private HttpClient client;
- private boolean done;
- private boolean cancelled;
-
- /**
- * Conflate external data
- *
- * @param category The category to use to determine the conflation server
- * @param openstreetmap The OSM data (may be null -- try to avoid this)
- * @param external The data to conflate (may not be null)
- */
- public DataConflationSender(MapWithAICategory category, DataSet openstreetmap, DataSet external) {
- Objects.requireNonNull(external, tr("We must have data to conflate"));
- Objects.requireNonNull(category, tr("We must have a category for the data"));
- this.osm = openstreetmap;
- this.external = external;
- this.category = category;
- }
-
- @Override
- public void run() {
- String url = MapWithAIConflationCategory.conflationUrlFor(category);
- if (!Utils.isStripEmpty(url) && !NetworkManager.isOffline(url)) {
- this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
- try {
- final var form = new TreeMap();
- if (osm != null) {
- final var output = new StringWriter();
- try (var writer = OsmWriterFactory.createOsmWriter(new PrintWriter(output), true, "0.6")) {
- writer.write(osm);
- form.put("openstreetmap", output.toString()); // APPLICATION_XML
- }
- }
- // We need to reset the writers to avoid writing previous streams
- final var output = new StringWriter();
- try (var writer = OsmWriterFactory.createOsmWriter(new PrintWriter(output), true, "0.6")) {
- writer.write(external);
- form.put("external", output.toString());
- }
-
- final var boundary = UUID.randomUUID().toString();
- final var request = HttpRequest.newBuilder(URI.create(url))
- .POST(HttpRequest.BodyPublishers.ofString(formEncodeMap(boundary, form)))
- .timeout(Duration.ofSeconds(10))
- .header("Content-Type", "multipart/form-data;boundary=" + boundary).build();
- final var response = client.send(request, HttpResponse.BodyHandlers.ofInputStream());
- if (response.statusCode() == 200) {
- conflatedData = OsmReader.parseDataSet(response.body(), NullProgressMonitor.INSTANCE,
- OsmReader.Options.SAVE_ORIGINAL_ID);
- } else {
- conflatedData = null;
- }
- Logging.info(request.method() + ' ' + url + " -> " + request.uri().getScheme() + '/'
- + response.version() + ' ' + response.statusCode());
- } catch (HttpTimeoutException httpTimeoutException) {
- final var oldThrowable = NetworkManager.addNetworkError(url, httpTimeoutException);
- if (oldThrowable != null) {
- Logging.trace(oldThrowable);
- }
- } catch (IOException | UnsupportedOperationException | IllegalDataException e) {
- Logging.error(e);
- } catch (InterruptedException e) {
- Logging.trace(e);
- if (!this.isCancelled()) {
- this.cancel(false);
- }
- Thread.currentThread().interrupt();
- }
- }
- this.done = true;
- synchronized (this) {
- this.notifyAll();
- }
- }
-
- @Override
- public boolean cancel(boolean mayInterruptIfRunning) {
- this.done = true;
- this.cancelled = true;
- if (this.client != null) {
- this.client.executor().ifPresent(Object::notifyAll);
- }
- this.client = null;
- synchronized (this) {
- this.notifyAll();
- }
- return true;
- }
-
- @Override
- public boolean isCancelled() {
- return this.cancelled;
- }
-
- @Override
- public boolean isDone() {
- return this.done;
- }
-
- @Override
- public DataSet get() throws InterruptedException, ExecutionException {
- synchronized (this) {
- while (!isDone()) {
- this.wait(100);
- }
- }
- return this.conflatedData;
- }
-
- @Override
- public DataSet get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
- long realtime = unit.toMillis(timeout);
- long waitTime = realtime > MAX_POLLS ? realtime / MAX_POLLS : 1;
- long timeWaited = 0;
- synchronized (this) {
- while (!isDone()) {
- this.wait(waitTime);
- timeWaited += waitTime;
- }
- }
- if (!isDone() && timeWaited > realtime) {
- throw new TimeoutException();
- }
- return this.conflatedData;
- }
-
- private static String formEncodeMap(String boundary, Map form) {
- final var separator = "--" + boundary + "\r\nContent-Disposition: form-data; name=";
- final var builder = new StringBuilder();
- for (var entry : form.entrySet()) {
- builder.append(separator).append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
- .append(";\r\nContent-Type: application/xml\r\n\r\n")
- .append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8)).append("\r\n");
- }
- builder.append("--").append(boundary).append("--");
- return builder.toString();
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DetectTaskingManagerUtils.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DetectTaskingManagerUtils.java
deleted file mode 100644
index 41c35c2e..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DetectTaskingManagerUtils.java
+++ /dev/null
@@ -1,94 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.time.Instant;
-import java.util.regex.Pattern;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.gpx.GpxData;
-import org.openstreetmap.josm.data.gpx.GpxRoute;
-import org.openstreetmap.josm.data.gpx.WayPoint;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.layer.GpxLayer;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-
-/**
- * Various methods to simplify detection of tasking manager tasks
- *
- * @author Taylor Smock
- *
- */
-final class DetectTaskingManagerUtils {
- public static final String MAPWITHAI_CROP_AREA = tr("{0}: Crop Area", MapWithAIPlugin.NAME);
- private static final Pattern[] PATTERNS = { Pattern.compile("^Task Boundaries.*$"),
- Pattern.compile("^" + MAPWITHAI_CROP_AREA + "$"), Pattern.compile("^Boundary for task:.*$") };
-
- private DetectTaskingManagerUtils() {
- // Hide since this is going to be a static class
- }
-
- /**
- * Check for a tasking manager layer
- *
- * @return True if there is a tasking manager layer
- */
- public static boolean hasTaskingManagerLayer() {
- return getTaskingManagerLayer() != null;
- }
-
- /**
- * Get a tasking manager layer
- *
- * @return A {@link Layer} that matches a pattern defined in
- * {@link DetectTaskingManagerUtils#PATTERNS} or {@code null}.
- */
- public static Layer getTaskingManagerLayer() {
- return MainApplication.getLayerManager().getLayers().stream().filter(tlayer -> Stream.of(PATTERNS).parallel()
- .anyMatch(pattern -> pattern.matcher(tlayer.getName()).matches())).findFirst().orElse(null);
- }
-
- /**
- * Get the bounds from the tasking manager layer
- *
- * @return A {@link Bounds} made from a tasking manager layer, or one that is
- * not valid.
- */
- public static Bounds getTaskingManagerBounds() {
- final var layer = getTaskingManagerLayer();
- if (layer instanceof GpxLayer gpxLayer) {
- final var realBounds = gpxLayer.data.recalculateBounds();
- return new Bounds(realBounds);
- } else if (layer instanceof OsmDataLayer osmDataLayer && osmDataLayer.getDataSet().getWays().size() == 1) {
- final var bbox = osmDataLayer.getDataSet().getWays().iterator().next().getBBox();
- final var returnBounds = new Bounds(bbox.getTopLeft());
- returnBounds.extend(bbox.getBottomRight());
- return returnBounds;
- }
- return new Bounds(0, 0, 0, 0);
- }
-
- /**
- * Create a GpxData that can be used to define a crop area
- *
- * @param bounds A bounds to crop data to
- * @return A gpx layer that can be used to crop data from MapWithAI
- */
- public static GpxData createTaskingManagerGpxData(Bounds bounds) {
- final var data = new GpxData();
- final var route = new GpxRoute();
- route.routePoints.add(new WayPoint(bounds.getMin()));
- route.routePoints.add(new WayPoint(new LatLon(bounds.getMaxLat(), bounds.getMinLon())));
- route.routePoints.add(new WayPoint(bounds.getMax()));
- route.routePoints.add(new WayPoint(new LatLon(bounds.getMinLat(), bounds.getMaxLon())));
- route.routePoints.add(route.routePoints.iterator().next());
- route.routePoints.forEach(waypoint -> waypoint.setInstant(Instant.EPOCH));
- data.addRoute(route);
- return data;
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DownloadListener.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DownloadListener.java
deleted file mode 100644
index 3cf88cb9..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DownloadListener.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import java.lang.ref.WeakReference;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.DataSource;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.DataSourceChangeEvent;
-import org.openstreetmap.josm.data.osm.DataSourceListener;
-import org.openstreetmap.josm.data.osm.event.DataSourceAddedEvent;
-import org.openstreetmap.josm.tools.Destroyable;
-
-/**
- * This class listens for download events, and then gets new data
- *
- * @author Taylor Smock
- *
- */
-public final class DownloadListener implements DataSourceListener, Destroyable {
-
- final WeakReference ds;
- private static final double BBOX_SIMILARITY_DEGREES = 0.001;
- private static final Collection LISTENERS = new HashSet<>();
-
- /**
- * Listen to downloads in a dataset
- *
- * @param dataSet The dataset to listen to
- */
- public DownloadListener(DataSet dataSet) {
- Objects.requireNonNull(dataSet, "DataSet cannot be null");
- ds = new WeakReference<>(dataSet);
- dataSet.addDataSourceListener(this);
- LISTENERS.add(this);
- }
-
- @Override
- public void dataSourceChange(DataSourceChangeEvent event) {
- if (event instanceof DataSourceAddedEvent) {
- final var layer = MapWithAIDataUtils.getLayer(false);
- if (layer == null) {
- destroy();
- return;
- }
- if (layer.downloadContinuous()) {
- final var bounds = DataSource.getDataSourceBounds(event.getSource().getDataSources());
- bounds.removeIf(a -> layer.getDataSet().getDataSourceBounds().stream().map(Bounds::toBBox)
- .anyMatch(b -> b.bboxIsFunctionallyEqual(a.toBBox(), BBOX_SIMILARITY_DEGREES)));
- MapWithAIDataUtils.getMapWithAIData(layer, bounds);
-
- }
- }
- }
-
- @Override
- public void destroy() {
- final var realDs = ds.get();
- if (realDs != null) {
- // Should be added, so no exception should be thrown
- realDs.removeDataSourceListener(this);
- ds.clear();
- LISTENERS.remove(this);
- }
- }
-
- /**
- * Destroy all download listeners for MapWithAI
- */
- public static void destroyAll() {
- new HashSet<>(LISTENERS).forEach(DownloadListener::destroy);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DownloadMapWithAITask.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DownloadMapWithAITask.java
deleted file mode 100644
index ce023149..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/DownloadMapWithAITask.java
+++ /dev/null
@@ -1,201 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.IOException;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ForkJoinTask;
-import java.util.concurrent.Future;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
-import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.io.OsmApiException;
-import org.openstreetmap.josm.io.OsmServerReader;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-import org.xml.sax.SAXException;
-
-/**
- * Download data from MapWithAI
- */
-public class DownloadMapWithAITask extends DownloadOsmTask {
- private final List urls;
-
- /**
- * Show common notifications/issues
- */
- private static final class Notifications {
- private Notifications() {
- // Hide constructor
- }
-
- /**
- * Show a notification that no data sources that are enabled have data in the
- * area.
- */
- public static void showEmptyNotification() {
- GuiHelper.runInEDT(Notifications::realShowEmptyNotification);
- }
-
- private static void realShowEmptyNotification() {
- final var n = new Notification();
- n.setIcon(ImageProvider.get("mapwithai"));
- n.setContent(tr("No enabled data sources have potential data in this area"));
- n.show();
- }
-
- /**
- * Show a notification that no MapWithAI layers are enabled.
- */
- public static void showNoLayerNotification() {
- GuiHelper.runInEDT(Notifications::realShowNoLayerNotification);
- }
-
- private static void realShowNoLayerNotification() {
- final var n = new Notification();
- n.setIcon(ImageProvider.get("mapwithai"));
- n.setContent(tr("No MapWithAI layers were selected. Please select at least one."));
- GuiHelper.runInEDT(n::show);
- }
- }
-
- /**
- * Create a new download task
- */
- public DownloadMapWithAITask() {
- urls = MapWithAILayerInfo.getInstance().getLayers();
- MapWithAILayerInfo.getInstance().save(); // Save preferences between downloads.
- }
-
- @Override
- public Future> download(OsmServerReader reader, DownloadParams settings, Bounds downloadArea,
- ProgressMonitor progressMonitor) {
- if (!urls.isEmpty()) {
- final var task = new DownloadTask(settings, tr("MapWithAI Download"), progressMonitor, false, false,
- downloadArea);
- return MainApplication.worker.submit(task);
- }
- Notifications.showNoLayerNotification();
- return CompletableFuture.completedFuture(null);
- }
-
- @Override
- public String getConfirmationMessage(URL url) {
- if (url != null) {
- final var items = new ArrayList<>();
- items.add(tr("OSM Server URL:") + ' ' + url.getHost());
- items.add(tr("Command") + ": " + url.getPath());
- if (url.getQuery() != null) {
- items.add(tr("Request details: {0}", url.getQuery().replaceAll(",\\s*", ", ")));
- }
- return Utils.joinAsHtmlUnorderedList(items);
- }
- return null;
- }
-
- class DownloadTask extends AbstractInternalTask {
- List> downloader;
- final Bounds bounds;
- private List relevantUrls;
-
- public DownloadTask(DownloadParams settings, String title, ProgressMonitor progressMonitor,
- boolean ignoreException, boolean zoomAfterDownload, Bounds bounds) {
- super(settings, title, progressMonitor, ignoreException, zoomAfterDownload);
- this.bounds = bounds;
- }
-
- @Override
- protected void cancel() {
- setCanceled(true);
- if (downloader != null) {
- downloader.forEach(task -> task.cancel(true));
- }
- }
-
- @Override
- protected void realRun() throws SAXException, IOException, OsmTransferException {
- relevantUrls = urls.stream().filter(i -> i.getBounds() == null || i.getBounds().intersects(bounds))
- .collect(Collectors.toList());
- final var monitor = getProgressMonitor();
- if (relevantUrls.isEmpty()) {
- Notifications.showEmptyNotification();
- return;
- }
- if (relevantUrls.size() < 5) {
- monitor.indeterminateSubTask(tr("MapWithAI Download"));
- } else {
- monitor.setTicksCount(relevantUrls.size());
- }
- downloadedData = new DataSet();
- final var pool = MapWithAIDataUtils.getForkJoinPool();
- this.downloader = new ArrayList<>(relevantUrls.size());
- for (MapWithAIInfo info : relevantUrls) {
- if (isCanceled()) {
- break;
- }
- this.downloader.add(pool.submit(MapWithAIDataUtils.download(this.progressMonitor, bounds, info,
- MapWithAIDataUtils.MAXIMUM_SIDE_DIMENSIONS)));
- }
- for (var task : this.downloader) {
- try {
- DownloadMapWithAITask.this.downloadedData.mergeFrom(task.get(),
- monitor.createSubTaskMonitor(1, false));
- } catch (CancellationException e) {
- Logging.trace(e);
- return;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- this.downloader.forEach(t -> t.cancel(true));
- throw new IOException(e);
- } catch (ExecutionException e) {
- // Throw the "original" exception type, if at all possible.
- Throwable current = e;
- while (current.getCause() != null && !current.equals(current.getCause())) {
- current = current.getCause();
- }
- if (current instanceof OsmApiException ex) {
- final var here = new OsmApiException(ex.getResponseCode(), ex.getErrorHeader(),
- ex.getErrorBody(), ex.getAccessedUrl(), ex.getLogin(), ex.getContentType());
- here.initCause(e);
- here.setUrl(here.getAccessedUrl());
- throw here;
- }
- if (current instanceof OsmTransferException) {
- throw new OsmTransferException(current.getMessage(), e);
- }
- throw new IOException(e);
- }
- }
- }
-
- @Override
- protected void finish() {
- if (!isCanceled() && !isFailed()) {
- synchronized (DownloadMapWithAITask.DownloadTask.class) {
- MapWithAILayer layer = MapWithAIDataUtils.getLayer(true);
- layer.getDataSet().mergeFrom(downloadedData);
- relevantUrls.forEach(layer::addDownloadedInfo);
- }
- GetDataRunnable.cleanup(MapWithAIDataUtils.getLayer(true).getDataSet(), null, null);
- }
- }
-
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/GetDataRunnable.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/GetDataRunnable.java
deleted file mode 100644
index 697b2cd2..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/GetDataRunnable.java
+++ /dev/null
@@ -1,746 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static java.util.function.Predicate.not;
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.io.Serial;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TreeMap;
-import java.util.concurrent.ForkJoinTask;
-import java.util.concurrent.RecursiveTask;
-import java.util.function.BiPredicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.actions.MergeNodesAction;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.DeleteCommand;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.coor.ILatLon;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.osm.AbstractPrimitive;
-import org.openstreetmap.josm.data.osm.BBox;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Hash;
-import org.openstreetmap.josm.data.osm.INode;
-import org.openstreetmap.josm.data.osm.IPrimitive;
-import org.openstreetmap.josm.data.osm.IRelation;
-import org.openstreetmap.josm.data.osm.IWay;
-import org.openstreetmap.josm.data.osm.IWaySegment;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Storage;
-import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.data.osm.TagMap;
-import org.openstreetmap.josm.data.osm.UploadPolicy;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
-import org.openstreetmap.josm.data.projection.ProjectionRegistry;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.commands.MergeDuplicateWays;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.PreConflatedDataUtils;
-import org.openstreetmap.josm.tools.Geometry;
-import org.openstreetmap.josm.tools.JosmRuntimeException;
-import org.openstreetmap.josm.tools.Pair;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * Get data in a parallel manner
- *
- * @author Taylor Smock
- */
-public class GetDataRunnable extends RecursiveTask {
- /**
- * This is functionally equivalent to
- * {@link org.openstreetmap.josm.data.validation.tests.DuplicateNode.NodeHash}
- */
- private static class ILatLonHash implements Hash {
- private static final double PRECISION = DEGREE_BUFFER;
-
- /**
- * Returns the rounded coordinated according to {@link #PRECISION}
- *
- * @param coor The coordinate to round
- * @return The rounded coordinate
- * @see LatLon#roundToOsmPrecision
- */
- private static ILatLon roundCoord(ILatLon coor) {
- return new LatLon(Math.round(coor.lat() / PRECISION) * PRECISION,
- Math.round(coor.lon() / PRECISION) * PRECISION);
- }
-
- @SuppressWarnings("unchecked")
- protected static ILatLon getLatLon(Object o) {
- if (o instanceof INode) {
- LatLon coor = ((INode) o).getCoor();
- if (coor == null)
- return null;
- if (PRECISION == 0)
- return coor.getRoundedToOsmPrecision();
- return roundCoord(coor);
- } else if (o instanceof List>) {
- LatLon coor = ((List) o).get(0).getCoor();
- if (coor == null)
- return null;
- if (PRECISION == 0)
- return coor.getRoundedToOsmPrecision();
- return roundCoord(coor);
- } else
- throw new AssertionError();
- }
-
- @Override
- public boolean equals(Object k, Object t) {
- ILatLon coorK = getLatLon(k);
- ILatLon coorT = getLatLon(t);
- return Objects.equals(coorK, coorT);
- }
-
- @Override
- public int getHashCode(Object k) {
- ILatLon coorK = getLatLon(k);
- return coorK == null ? 0 : coorK.hashCode();
- }
- }
-
- /**
- * This checks that all visited objects are highways
- */
- private static final class HighwayVisitor implements PrimitiveVisitor {
- boolean onlyHighways = true;
-
- @Override
- public void visit(INode n) {
- onlyHighways = false;
- }
-
- @Override
- public void visit(IWay> w) {
- if (!w.isTagged() || !w.hasTag("highway")) {
- onlyHighways = false;
- }
- }
-
- @Override
- public void visit(IRelation> r) {
- onlyHighways = false;
- }
- }
-
- @Serial
- private static final long serialVersionUID = 258423685658089715L;
- private final transient List runnableBounds;
- private final transient DataSet dataSet;
- private final transient ProgressMonitor monitor;
- private static final float DEGREE_BUFFER = 0.001f;
- private static final int MAX_LATITUDE = 90;
- private static final int MAX_LONGITUDE = 180;
-
- private Integer maximumDimensions;
- private transient MapWithAIInfo info;
-
- private static final int MAX_NUMBER_OF_BBOXES_TO_PROCESS = 1;
- private static final String SERVER_ID_KEY = "current_id";
-
- /** An equals sign (=) used for tag splitting */
- private static final String EQUALS = "=";
-
- private static final double ARTIFACT_ANGLE = 0.1745; // 10 degrees in radians
-
- /**
- * The source tag to be used to populate source values. Not seen on objects
- * post-upload.
- */
- public static final String MAPWITHAI_SOURCE_TAG_KEY = "mapwithai:source";
- /**
- * The source tag to be added to all objects from the source. Seen on objects
- * post-upload.
- */
- public static final String SOURCE_TAG_KEY = "source";
-
- /**
- * Get data in the background
- *
- * @param bbox The initial bbox to get data from (don't reduce beforehand --
- * it will be reduced here)
- * @param dataSet The dataset to add the data to
- * @param monitor A monitor to keep track of progress
- */
- public GetDataRunnable(Bounds bbox, DataSet dataSet, ProgressMonitor monitor) {
- this(Collections.singletonList(bbox), dataSet, monitor);
- }
-
- /**
- * Get data in the background
- *
- * @param bbox The initial bboxes to get data from (don't reduce beforehand
- * -- it will be reduced here)
- * @param dataSet The dataset to add the data to
- * @param monitor A monitor to keep track of progress
- */
- public GetDataRunnable(List bbox, DataSet dataSet, ProgressMonitor monitor) {
- super();
- this.runnableBounds = bbox.stream().distinct().collect(Collectors.toList());
- this.dataSet = dataSet;
- this.monitor = Optional.ofNullable(monitor).orElse(NullProgressMonitor.INSTANCE);
- }
-
- /**
- * Set the maximum download bbox size. Must be called before execution.
- *
- * @param maximumDimensions The maximum bbox download size
- */
- public void setMaximumDimensions(int maximumDimensions) {
- this.maximumDimensions = maximumDimensions;
- }
-
- @Override
- public DataSet compute() {
- final var bounds = maximumDimensions == null ? MapWithAIDataUtils.reduceBoundSize(runnableBounds)
- : MapWithAIDataUtils.reduceBoundSize(runnableBounds, maximumDimensions);
- monitor.beginTask(tr("Downloading {0} data ({1} total downloads)", MapWithAIPlugin.NAME, bounds.size()),
- bounds.size() - 1);
- if (!monitor.isCanceled()) {
- if (bounds.size() == MAX_NUMBER_OF_BBOXES_TO_PROCESS) {
- final var temporaryDataSet = getDataReal(bounds.get(0), monitor);
- this.dataSet.update(() -> dataSet.mergeFrom(temporaryDataSet));
- } else {
- final Collection tasks = bounds.stream()
- .map(bound -> new GetDataRunnable(bound, dataSet, monitor.createSubTaskMonitor(0, true)))
- .toList();
- tasks.forEach(GetDataRunnable::fork);
- tasks.forEach(runnable -> {
- runnable.join();
- monitor.worked(1);
- });
- }
- }
- // This can technically be included in the above block, but it is here so that
- // cancellation is a little faster
- if (!monitor.isCanceled() && !bounds.isEmpty()) {
- cleanup(dataSet, bounds.get(0), info);
- }
- monitor.finishTask();
- return dataSet;
- }
-
- /**
- * Perform cleanups on a dataset (one dataset at a time)
- *
- * @param dataSet The dataset to cleanup
- * @param bounds The newly added bounds to the dataset. May be {@code null}.
- * @param info The information used to download the data
- */
- public static void cleanup(DataSet dataSet, Bounds bounds, MapWithAIInfo info) {
- dataSet.update(() -> realCleanup(dataSet, bounds, info));
- }
-
- private static void realCleanup(DataSet dataSet, Bounds bounds, MapWithAIInfo info) {
- final Bounds boundsToUse;
- if (bounds == null && !dataSet.getDataSourceBounds().isEmpty()) {
- boundsToUse = new Bounds(dataSet.getDataSourceBounds().get(0));
- dataSet.getDataSourceBounds().forEach(boundsToUse::extend);
- } else if (bounds == null) {
- boundsToUse = new Bounds(0, 0, 0, 0);
- } else {
- boundsToUse = new Bounds(bounds);
- }
- replaceTags(dataSet);
- removeCommonTags(dataSet);
- removeEmptyTags(dataSet, bounds);
- mergeNodes(dataSet);
- cleanupDataSet(dataSet);
- mergeWays(dataSet);
- PreConflatedDataUtils.removeConflatedData(dataSet, info);
- removeAlreadyAddedData(dataSet);
- final var ways = dataSet.searchWays(boundsToUse.toBBox()).stream().filter(w -> w.hasKey("highway")).toList();
- if (!ways.isEmpty()) {
- new MergeDuplicateWays(dataSet, ways).executeCommand();
- }
- (boundsToUse.isCollapsed() || boundsToUse.isOutOfTheWorld() ? dataSet.getWays()
- : dataSet.searchWays(boundsToUse.toBBox())).stream().filter(way -> !way.isDeleted())
- .forEach(GetDataRunnable::cleanupArtifacts);
- }
-
- /**
- * Remove empty tags from primitives
- *
- * @param dataSet The dataset to remove tags from
- * @param bounds The bounds to remove the empty tags from (performance)
- */
- public static void removeEmptyTags(DataSet dataSet, Bounds bounds) {
- Bounds boundsToUse;
- if (bounds == null && !dataSet.getDataSourceBounds().isEmpty()) {
- boundsToUse = new Bounds(dataSet.getDataSourceBounds().get(0));
- dataSet.getDataSourceBounds().forEach(boundsToUse::extend);
- } else if (bounds == null) {
- boundsToUse = new Bounds(-MAX_LATITUDE, -MAX_LONGITUDE, MAX_LATITUDE, MAX_LONGITUDE);
- } else {
- boundsToUse = new Bounds(bounds);
- }
- dataSet.searchPrimitives(boundsToUse.toBBox()).forEach(GetDataRunnable::realRemoveEmptyTags);
- }
-
- /**
- * Remove empty tags from primitives. We assume that the `getKey` implementation
- * returns a new map.
- *
- * @param prim The primitive to remove empty tags from.
- */
- private static void realRemoveEmptyTags(IPrimitive prim) {
- final var keys = prim.getKeys();
- for (var entry : keys.entrySet()) {
- if (entry.getValue() == null || entry.getValue().trim().isEmpty()) {
- // The OsmPrimitives all return a new map, so this is safe.
- prim.remove(entry.getKey());
- }
- }
- }
-
- /**
- * Remove ways that have already been added to an OSM layer
- *
- * @param dataSet The dataset with potential duplicate ways (it is modified)
- */
- public static void removeAlreadyAddedData(DataSet dataSet) {
- final var osmData = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
- .map(OsmDataLayer::getDataSet).filter(ds -> !ds.equals(dataSet)).toList();
- for (var way : dataSet.getWays()) {
- if (!way.isDeleted() && way.getOsmId() <= 0) {
- for (var ds : osmData) {
- if (checkIfPrimitiveDuplicatesPrimitiveInDataSet(way, ds)) {
- final var nodes = way.getNodes();
- Optional.ofNullable(DeleteCommand.delete(Collections.singleton(way), true, true))
- .ifPresent(Command::executeCommand);
- for (var node : nodes) {
- if (!node.isDeleted()
- && node.referrers(OsmPrimitive.class).allMatch(OsmPrimitive::isDeleted)) {
- node.setDeleted(true);
- }
- }
- break;
- }
- }
- }
- }
- }
-
- private static boolean checkIfPrimitiveDuplicatesPrimitiveInDataSet(OsmPrimitive primitive, DataSet ds) {
- final var possibleDuplicates = searchDataSet(ds, primitive);
- for (var dupe : possibleDuplicates) {
- if (checkIfProbableDuplicate(dupe, primitive)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean checkIfProbableDuplicate(OsmPrimitive one, OsmPrimitive two) {
- var equivalent = false;
- final TagMap oneMap = one.getKeys();
- final TagMap twoMap = two.getKeys();
- oneMap.remove(MAPWITHAI_SOURCE_TAG_KEY);
- twoMap.remove(MAPWITHAI_SOURCE_TAG_KEY);
- if (one.getClass().equals(two.getClass()) && oneMap.equals(twoMap)) {
- if (one instanceof Node coor1) {
- final ILatLon coor2 = ((Node) two);
- if (one.hasSameInterestingTags(two) && coor1.isLatLonKnown() && coor2.isLatLonKnown()
- && coor1.equalsEpsilon(coor2)) {
- equivalent = true;
- }
- } else if (one instanceof Way wayOne) {
- equivalent = wayOne.getNodes().stream().filter(Objects::nonNull)
- .allMatch(node1 -> ((Way) two).getNodes().stream().anyMatch(node1::equalsEpsilon));
- } else if (one instanceof Relation oneRelation) {
- equivalent = oneRelation.getMembers().stream()
- .allMatch(member1 -> ((Relation) two).getMembers().stream()
- .anyMatch(member2 -> member1.getRole().equals(member2.getRole())
- && checkIfProbableDuplicate(member1.getMember(), member2.getMember())));
- }
- }
- return equivalent;
- }
-
- private static List searchDataSet(DataSet ds, T primitive) {
- if (primitive instanceof OsmPrimitive osmPrimitive) {
- final var tBBox = new BBox();
- tBBox.addPrimitive(osmPrimitive, DEGREE_BUFFER);
- if (primitive instanceof Node) {
- return Collections.unmodifiableList(ds.searchNodes(tBBox));
- } else if (primitive instanceof Way) {
- return Collections.unmodifiableList(ds.searchWays(tBBox));
- } else if (primitive instanceof Relation) {
- return Collections.unmodifiableList(ds.searchRelations(tBBox));
- }
- }
- return Collections.emptyList();
- }
-
- /**
- * Replace tags in a dataset with a set of replacement tags
- *
- * @param dataSet The dataset with primitives to change
- */
- public static void replaceTags(DataSet dataSet) {
- final var replaceTags = MapWithAIPreferenceHelper.getReplacementTags().entrySet().stream()
- .filter(entry -> entry.getKey().contains(EQUALS) && entry.getValue().contains(EQUALS))
- .map(entry -> new Pair<>(Tag.ofString(entry.getKey()), Tag.ofString(entry.getValue())))
- .collect(Collectors.toMap(pair -> pair.a, pair -> pair.b));
- MapWithAIPreferenceHelper.getReplacementTags().entrySet().stream()
- .filter(entry -> !entry.getKey().equals(EQUALS) && Utils.isStripEmpty(entry.getValue()))
- .map(entry -> new Tag(entry.getKey(), null)).forEach(tag -> replaceTags.put(tag, tag));
- replaceTags(dataSet, replaceTags);
- }
-
- /**
- * Replace tags in a dataset with a set of replacement tags
- *
- * @param dataSet The dataset with primitives to change
- * @param replaceTags The tags to replace
- */
- public static void replaceTags(DataSet dataSet, Map replaceTags) {
- replaceTags.forEach((orig, replace) -> dataSet.allNonDeletedPrimitives().stream()
- .filter(prim -> prim.hasTag(orig.getKey(), orig.getValue())
- || (prim.hasKey(orig.getKey()) && Utils.isStripEmpty(orig.getValue())))
- .forEach(prim -> prim.put(replace)));
- }
-
- /**
- * Replace tags in a dataset with a set of replacement tags
- *
- * @param dataSet The dataset with primitives to change
- * @param replaceKeys The keys to replace (does not replace values)
- */
- public static void replaceKeys(DataSet dataSet, Map replaceKeys) {
- replaceKeys.entrySet().stream().filter(e -> !e.getKey().equals(e.getValue())).forEach(
- e -> dataSet.allNonDeletedPrimitives().stream().filter(p -> p.hasKey(e.getKey())).forEach(p -> {
- p.put(e.getValue(), p.get(e.getKey()));
- p.remove(e.getKey());
- }));
- }
-
- private static void cleanupDataSet(DataSet dataSet) {
- var origIds = dataSet.allPrimitives().stream().filter(prim -> prim.hasKey(MergeDuplicateWays.ORIG_ID))
- .distinct().collect(Collectors.toMap(prim -> prim, prim -> prim.get(MergeDuplicateWays.ORIG_ID)));
- final var serverIds = dataSet.allPrimitives().stream().filter(prim -> prim.hasKey(SERVER_ID_KEY)).distinct()
- .collect(Collectors.toMap(prim -> prim, prim -> prim.get(SERVER_ID_KEY)));
-
- final var toDelete = origIds.entrySet().stream().filter(entry -> serverIds.containsValue(entry.getValue()))
- .map(Map.Entry::getKey).collect(Collectors.toList());
- if (!toDelete.isEmpty()) {
- new DeleteCommand(toDelete).executeCommand();
- }
- origIds = origIds.entrySet().stream().filter(entry -> !toDelete.contains(entry.getKey()))
- .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
- serverIds.forEach((prim, str) -> prim.remove(SERVER_ID_KEY));
- origIds.forEach((prim, str) -> prim.remove(MergeDuplicateWays.ORIG_ID));
- }
-
- /**
- * Remove common tags from the dataset
- *
- * @param dataSet The dataset to remove tags from
- */
- public static void removeCommonTags(DataSet dataSet) {
- final var emptyNodes = new HashSet();
- for (var tagged : dataSet.allPrimitives()) {
- if (!tagged.hasKeys()) {
- continue;
- }
- if (tagged.hasKey(MergeDuplicateWays.ORIG_ID)) {
- tagged.remove(MergeDuplicateWays.ORIG_ID);
- }
- if (tagged instanceof Node node) {
- tagged.remove(SERVER_ID_KEY);
- if (!node.isDeleted() && !node.hasKeys() && node.getReferrers().isEmpty()) {
- emptyNodes.add(node);
- }
- }
- }
- if (!emptyNodes.isEmpty()) {
- new DeleteCommand(emptyNodes).executeCommand();
- }
- }
-
- /**
- * Create an efficient collection ({@link Storage}) of {@link List} of
- * {@link Node}s and {@link Node} objects
- *
- * @param dataSet The dataset to get nodes from
- * @return The storage to use
- */
- private static Storage generateEfficientNodeSearchStorage(DataSet dataSet) {
- final Storage nodes = new Storage<>(new ILatLonHash());
- for (Node node : dataSet.getNodes()) {
- if (!node.isDeleted()) {
- final Object old = nodes.get(node);
- if (old == null) {
- nodes.put(node);
- } else if (old instanceof Node oldNode) {
- List list = new ArrayList<>(2);
- list.add(oldNode);
- list.add(node);
- nodes.put(list);
- } else {
- @SuppressWarnings("unchecked")
- List list = (List) old;
- list.add(node);
- }
- }
- }
- return nodes;
- }
-
- /**
- * Merge nodes that have the same tags and (almost) the same location
- *
- * @param dataSet The dataset to merge nodes in
- */
- private static void mergeNodes(DataSet dataSet) {
- final var nodes = generateEfficientNodeSearchStorage(dataSet);
- for (var obj : nodes) {
- // We only care if there are multiple nodes at the location
- if (obj instanceof List>) {
- @SuppressWarnings("unchecked")
- final var iNodes = (List) obj;
- for (var nearNode : iNodes) {
- final var nearbyNodes = new ArrayList<>(iNodes);
- nearbyNodes.removeIf(node -> !nearNode.hasSameInterestingTags(node) || !usableNode(nearNode, node));
- final var mergeCommand = MergeNodesAction.mergeNodes(nearbyNodes, nearNode);
- if (mergeCommand != null) {
- mergeCommand.executeCommand();
- }
- }
- }
- }
- }
-
- private static boolean usableNode(Node nearNode, Node node) {
- return basicNodeChecks(nearNode, node) && onlyHasHighwayParents(node)
- && ((keyCheck(nearNode, node)
- && distanceCheck(nearNode, node, MapWithAIPreferenceHelper.getMaxNodeDistance()))
- || (nearNode.hasKeys() && node.hasKeys() && nearNode.getKeys().equals(node.getKeys())
- && distanceCheck(nearNode, node, MapWithAIPreferenceHelper.getMaxNodeDistance() * 10)));
- }
-
- private static boolean distanceCheck(Node nearNode, Node node, Double distance) {
- return Geometry.getDistance(nearNode, node) < distance;
- }
-
- private static boolean keyCheck(INode nearNode, INode node) {
- return !nearNode.hasKeys() || !node.hasKeys() || nearNode.getKeys().equals(node.getKeys());
- }
-
- private static boolean onlyHasHighwayParents(Node node) {
- final var highwayVisitor = new HighwayVisitor();
- node.visitReferrers(highwayVisitor);
- return highwayVisitor.onlyHighways;
- }
-
- private static boolean basicNodeChecks(INode nearNode, INode node) {
- return node != null && nearNode != null && !node.isDeleted() && !nearNode.isDeleted() && !nearNode.equals(node)
- && node.isLatLonKnown() && nearNode.isLatLonKnown();
- }
-
- private static void mergeWays(DataSet dataSet) {
- for (final var way1 : dataSet.getWays()) {
- if (way1.isDeleted()) {
- continue;
- }
- final var bbox = new BBox();
- bbox.addPrimitive(way1, DEGREE_BUFFER);
- for (var nearbyWay : dataSet.searchWays(bbox)) {
- if (nearbyWay.getNodes().stream().filter(way1::containsNode).count() > 1) {
- for (var entry : checkWayDuplications(way1, nearbyWay).entrySet()) {
- GetDataRunnable.addMissingElement(entry);
- }
- }
- }
- }
- }
-
- protected static void addMissingElement(Map.Entry, List>> entry) {
- final var way = entry.getKey().getWay();
- final Way waySegmentWay;
- try {
- waySegmentWay = entry.getKey().toWay();
- } catch (ReflectiveOperationException e) {
- throw new JosmRuntimeException(e);
- }
- final var toAdd = entry.getValue().stream().flatMap(seg -> Stream.of(seg.getFirstNode(), seg.getSecondNode()))
- .filter(node -> !waySegmentWay.containsNode(node)).findAny().orElse(null);
- if ((toAdd != null) && (convertToMeters(
- Geometry.getDistance(waySegmentWay, toAdd)) < (MapWithAIPreferenceHelper.getMaxNodeDistance() * 10))) {
- way.addNode(entry.getKey().getUpperIndex(), toAdd);
- }
- for (var i = 0; i < (way.getNodesCount() - 2); i++) {
- final var node0 = way.getNode(i);
- final var node3 = way.getNode(i + 2);
- if (node0.equals(node3)) {
- final var nodes = way.getNodes();
- nodes.remove(i + 2); // NOSONAR SonarLint doesn't like this (if it was i instead of i + 2, it would
- // be an issue)
- way.setNodes(nodes);
- }
- }
- }
-
- protected static double convertToMeters(double value) {
- return value * ProjectionRegistry.getProjection().getMetersPerUnit();
- }
-
- protected static void cleanupArtifacts(Way way) {
- for (var i = 0; i < (way.getNodesCount() - 2); i++) {
- final var node0 = way.getNode(i);
- final var node1 = way.getNode(i + 1);
- final var node2 = way.getNode(i + 2);
- final double angle = Geometry.getCornerAngle(node0.getEastNorth(), node1.getEastNorth(),
- node2.getEastNorth());
- if (angle < ARTIFACT_ANGLE) {
- final List nodes = way.getNodes();
- nodes.remove(i + 1); // NOSONAR not an issue since I'm adding it back
- nodes.add(i + 2, node1);
- }
- }
- if ((way.getNodesCount() == 2) && (way.getDataSet() != null)) {
- final var tBBox = new BBox();
- tBBox.addPrimitive(way, DEGREE_BUFFER);
- if (way.getDataSet().searchWays(tBBox).stream().filter(not(IPrimitive::isDeleted)).filter(not(way::equals))
- .anyMatch(tWay -> way.getNodes().stream().filter(
- tNode -> Geometry.getDistance(tNode, tWay) < MapWithAIPreferenceHelper.getMaxNodeDistance())
- .count() == way.getNodesCount())) {
- way.setDeleted(true);
- }
- }
- }
-
- /**
- * Check for nearly duplicate way sections
- *
- * @param way1 The way to map duplicate segments to
- * @param way2 The way that may have duplicate segments
- * @return A Map<WaySegment to modify from way1, List<WaySegments from
- * way2> to make the segment conform to >
- */
- protected static Map, List>> checkWayDuplications(Way way1,
- Way way2) {
- final var waySegments1 = way1.getNodePairs(false).stream()
- .map(pair -> IWaySegment.forNodePair(way1, pair.a, pair.b)).toList();
- final var waySegments2 = way2.getNodePairs(false).stream()
- .map(pair -> IWaySegment.forNodePair(way2, pair.a, pair.b)).toList();
- final var partials = new TreeMap, List>>();
- final BiPredicate, IWaySegment> connected = (segment1,
- segment2) -> segment1.getFirstNode().equals(segment2.getFirstNode())
- || segment1.getSecondNode().equals(segment2.getFirstNode())
- || segment1.getFirstNode().equals(segment2.getSecondNode())
- || segment1.getSecondNode().equals(segment2.getSecondNode());
- for (final var segment1 : waySegments1) {
- final var replacements = waySegments2.stream().filter(seg2 -> connected.test(segment1, seg2))
- .filter(seg -> {
- final var node2 = segment1.getFirstNode().equals(seg.getFirstNode())
- || segment1.getSecondNode().equals(seg.getFirstNode()) ? seg.getFirstNode()
- : seg.getSecondNode();
- final var node1 = node2.equals(seg.getFirstNode()) ? seg.getSecondNode() : seg.getFirstNode();
- final var node3 = segment1.getFirstNode().equals(node2) ? segment1.getSecondNode()
- : segment1.getFirstNode();
- return Math.abs(Geometry.getCornerAngle(node1.getEastNorth(), node2.getEastNorth(),
- node3.getEastNorth())) < (Math.PI / 4);
- }).collect(Collectors.toList());
- if ((replacements.size() != 2) || replacements.stream()
- .anyMatch(seg -> Arrays.asList(segment1.getFirstNode(), segment1.getSecondNode())
- .containsAll(Arrays.asList(seg.getFirstNode(), seg.getSecondNode())))) {
- continue;
- }
- partials.put(segment1, replacements);
- }
- return partials;
- }
-
- /**
- * Actually get the data
- *
- * @param bounds The bounds to get the data from
- * @param monitor Use to determine if the operation has been cancelled
- * @return A dataset with the data from the bounds
- */
- private static DataSet getDataReal(Bounds bounds, ProgressMonitor monitor) {
- final var dataSet = new DataSet();
- dataSet.setUploadPolicy(UploadPolicy.DISCOURAGED);
-
- final var tasks = new ArrayList>();
- final var pool = MapWithAIDataUtils.getForkJoinPool();
- for (var map : new ArrayList<>(MapWithAILayerInfo.getInstance().getLayers())) {
- tasks.add(pool.submit(
- MapWithAIDataUtils.download(monitor, bounds, map, MapWithAIDataUtils.MAXIMUM_SIDE_DIMENSIONS)));
- }
- for (var task : tasks) {
- dataSet.mergeFrom(task.join());
- }
- dataSet.setUploadPolicy(UploadPolicy.BLOCKED);
- return dataSet;
- }
-
- /**
- * Add source tags to primitives
- *
- * @param dataSet The dataset to add the mapwithai source tag to (not visible on
- * object post-upload)
- * @param source The source to associate with the data
- * @return The dataset for easy chaining
- */
- public static DataSet addMapWithAISourceTag(DataSet dataSet, String source) {
- return addTag(dataSet, MAPWITHAI_SOURCE_TAG_KEY, source);
- }
-
- /**
- * Add source tags to primitives
- *
- * @param dataSet The dataset to add the source tag to (visible on object
- * post-upload)
- * @param source The source to associate with the data
- * @return The dataset for easy chaining
- */
- public static DataSet addSourceTag(DataSet dataSet, String source) {
- return addTag(dataSet, SOURCE_TAG_KEY, source);
- }
-
- private static DataSet addTag(DataSet dataSet, String key, String value) {
- dataSet.getNodes().stream().filter(p -> checkIfMapWithAISourceShouldBeAdded(p, key))
- .filter(node -> node.getReferrers().isEmpty()).forEach(node -> node.put(key, value));
- dataSet.getWays().stream().filter(p -> checkIfMapWithAISourceShouldBeAdded(p, key))
- .forEach(way -> way.put(key, value));
- dataSet.getRelations().stream().filter(p -> checkIfMapWithAISourceShouldBeAdded(p, key))
- .forEach(rel -> rel.put(key, value));
- return dataSet;
- }
-
- private static boolean checkIfMapWithAISourceShouldBeAdded(OsmPrimitive prim, String key) {
- return !prim.isDeleted() && !prim.hasTag(key);
- }
-
- /**
- * Set the info that is being used to download data
- *
- * @param info The info being used
- */
- public void setMapWithAIInfo(MapWithAIInfo info) {
- this.info = info;
- }
-
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIAction.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIAction.java
deleted file mode 100644
index 8829fd12..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIAction.java
+++ /dev/null
@@ -1,161 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static java.util.function.Predicate.not;
-import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
-import static org.openstreetmap.josm.tools.I18n.marktr;
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import javax.swing.Action;
-import javax.swing.JOptionPane;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.KeyEvent;
-import java.io.Serial;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.actions.AbstractMergeAction;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.tools.Shortcut;
-
-/**
- * Create or download MapWithAI data
- */
-public class MapWithAIAction extends JosmAction {
- /** UID */
- @Serial
- private static final long serialVersionUID = 8886705479253246588L;
- private static final String DOWNLOAD_DATA = marktr("{0}: Download Data");
- private static final String SWITCH_LAYERS = marktr("{0}: Switch Layers");
-
- /**
- * Create the action
- */
- public MapWithAIAction() {
- super(tr(DOWNLOAD_DATA, MapWithAIPlugin.NAME), "mapwithai", tr("Get data from {0}", MapWithAIPlugin.NAME),
- Shortcut.registerShortcut("data:mapWithAI", tr("Data: {0}", MapWithAIPlugin.NAME), KeyEvent.VK_R,
- Shortcut.CTRL),
- true, "mapwithai:downloadData", true);
- setHelpId(ht("Plugin/MapWithAI#BasicUsage"));
- }
-
- @Override
- public void actionPerformed(ActionEvent event) {
- if (isEnabled()) {
- final boolean hasLayer = MapWithAIDataUtils.getLayer(false) != null;
- final var osmLayers = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
- .filter(not(MapWithAILayer.class::isInstance)).filter(Layer::isVisible)
- .collect(Collectors.toList());
- final var layer = getOsmLayer(osmLayers);
- if ((layer != null) && MapWithAIDataUtils.getMapWithAIData(MapWithAIDataUtils.getLayer(true), layer)) {
- final var notification = createMessageDialog();
- if (notification != null) {
- notification.show();
- }
- } else if ((layer != null) && hasLayer) {
- toggleLayer(layer);
- }
- }
- }
-
- /**
- * Get the osm layer that the user wants to use to get data from (doesn't ask if
- * user only has one data layer)
- *
- * @param osmLayers The list of osm data layers
- * @return The layer that the user selects
- */
- protected static OsmDataLayer getOsmLayer(List osmLayers) {
- OsmDataLayer returnLayer = null;
- final var tLayers = new ArrayList<>(osmLayers);
- if (DetectTaskingManagerUtils.hasTaskingManagerLayer()) {
- tLayers.removeIf(DetectTaskingManagerUtils.getTaskingManagerLayer()::equals);
- }
- if (tLayers.size() == 1) {
- returnLayer = osmLayers.get(0);
- } else if (!tLayers.isEmpty()) {
- returnLayer = AbstractMergeAction.askTargetLayer(osmLayers.toArray(new OsmDataLayer[0]),
- tr("Please select the initial layer for boundaries"), tr("Select target layer for boundaries"),
- tr("OK"), "download");
- }
- return returnLayer;
- }
-
- /**
- * Toggle the layer (the toLayer is the layer to switch to, if currently active
- * it will switch to the MapWithAI layer, if the MapWithAI layer is currently
- * active it will switch to the layer passed)
- *
- * @param toLayer The {@link Layer} to switch to
- */
- protected static void toggleLayer(Layer toLayer) {
- final var mapwithai = MapWithAIDataUtils.getLayer(false);
- final var currentLayer = MainApplication.getLayerManager().getActiveLayer();
- if (currentLayer != null) {
- if (currentLayer.equals(mapwithai) && (toLayer != null)) {
- MainApplication.getLayerManager().setActiveLayer(toLayer);
- } else if (currentLayer.equals(toLayer) && (mapwithai != null)) {
- MainApplication.getLayerManager().setActiveLayer(mapwithai);
- }
- }
- }
-
- @Override
- protected void updateEnabledState() {
- setEnabled(getLayerManager().getEditDataSet() != null);
- if (this.isEnabled()) {
- if (MapWithAIDataUtils.getLayer(false) == null) {
- putValue(Action.NAME, tr(DOWNLOAD_DATA, MapWithAIPlugin.NAME));
- } else {
- putValue(Action.NAME, tr(SWITCH_LAYERS, MapWithAIPlugin.NAME));
- }
- }
- }
-
- /**
- * Create a message dialog to notify the user if data is available in their
- * downloaded region
- *
- * @return A Notification to show ({@link Notification#show})
- */
- public static Notification createMessageDialog() {
- final var layer = MapWithAIDataUtils.getLayer(false);
- final var notification = layer == null ? null : new Notification();
- if (notification != null) {
- final var bounds = new ArrayList<>(layer.getDataSet().getDataSourceBounds());
- if (bounds.isEmpty()) {
- MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
- .map(OsmDataLayer::getDataSet).filter(Objects::nonNull).map(DataSet::getDataSourceBounds)
- .forEach(bounds::addAll);
- }
- final var message = new StringBuilder();
- message.append(MapWithAIPlugin.NAME).append(": ");
- DataAvailability.getInstance(); // force initialization, if it hasn't already occured
- final var availableTypes = new TreeMap();
- for (final var bound : bounds) {
- DataAvailability.getDataTypes(bound.getCenter())
- .forEach((type, available) -> availableTypes.merge(type, available, Boolean::logicalOr));
- }
- if (availableTypes.isEmpty()) {
- message.append(tr("No data available"));
- } else {
- message.append("Data available: ").append(String.join(", ", availableTypes.keySet()));
- }
-
- notification.setContent(message.toString());
- notification.setDuration(Notification.TIME_DEFAULT);
- notification.setIcon(JOptionPane.INFORMATION_MESSAGE);
- }
- return notification;
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIDataUtils.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIDataUtils.java
deleted file mode 100644
index c5977de3..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIDataUtils.java
+++ /dev/null
@@ -1,503 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static java.util.function.Predicate.not;
-import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import javax.swing.JOptionPane;
-
-import java.net.SocketTimeoutException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TreeSet;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.ForkJoinTask;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.UndoRedoHandler;
-import org.openstreetmap.josm.data.coor.ILatLon;
-import org.openstreetmap.josm.data.coor.LatLon;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
-import org.openstreetmap.josm.gui.ExceptionDialogUtil;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.io.IllegalDataException;
-import org.openstreetmap.josm.io.OsmTransferException;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.commands.MapWithAIAddCommand;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
-import org.openstreetmap.josm.tools.ExceptionUtil;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * Various utility methods
- *
- * @author Taylor Smock
- *
- */
-public final class MapWithAIDataUtils {
- /** The maximum dimensions for MapWithAI data (in kilometers) */
- public static final int MAXIMUM_SIDE_DIMENSIONS = 10_000; // RapiD is about 1 km, max is 10 km, but 10 km causes
- // timeouts
- private static final int TOO_MANY_BBOXES = 4;
- /**
- * {@code true} if we need a fork join pool that is not the
- * {@link ForkJoinPool#commonPool()}
- */
- private static Boolean requiresForkJoinPool;
- private static ForkJoinPool forkJoinPool;
- static final Object LAYER_LOCK = new Object();
-
- private MapWithAIDataUtils() {
- // Hide the constructor
- }
-
- /**
- * Add primitives and their children to a collection
- *
- * @param collection A collection to add the primitives to
- * @param primitives The primitives to add to the collection
- */
- public static void addPrimitivesToCollection(Collection collection,
- Collection primitives) {
- final var temporaryCollection = new TreeSet();
- for (final var primitive : primitives) {
- if (primitive instanceof Way way) {
- temporaryCollection.addAll(way.getNodes());
- } else if (primitive instanceof Relation relation) {
- addPrimitivesToCollection(temporaryCollection, relation.getMemberPrimitives());
- }
- temporaryCollection.add(primitive);
- }
- collection.addAll(temporaryCollection);
- }
-
- /**
- * Add specified source tags to objects without a source tag that also have a
- * specific key
- *
- * @param dataSet The {#link DataSet} to look through
- * @param primaryKey The primary key that must be in the {@link OsmPrimitive}
- * @param source The specified source value (not tag)
- */
- public static void addSourceTags(DataSet dataSet, String primaryKey, String source) {
- dataSet.allPrimitives().stream().filter(p -> p.hasKey(primaryKey) && !p.hasKey("source")).forEach(p -> {
- p.put("source", source);
- p.save();
- });
- }
-
- /**
- * Get a dataset from the API servers using a list bounds
- *
- * @param bounds The bounds from which to get data
- * @param maximumDimensions The maximum dimensions to try to download at any one
- * time
- * @return A DataSet with data inside the bounds
- */
- public static DataSet getData(Collection bounds, int maximumDimensions) {
- final var dataSet = new DataSet();
- final var realBounds = bounds.stream().filter(b -> !b.isOutOfTheWorld()).distinct()
- .flatMap(bound -> MapWithAIDataUtils.reduceBoundSize(bound, maximumDimensions).stream())
- .collect(Collectors.toList());
- if (!MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty()) {
- if ((bounds.size() < TOO_MANY_BBOXES) || confirmBigDownload(realBounds)) {
- final var monitor = new PleaseWaitProgressMonitor();
- monitor.beginTask(tr("Downloading {0} Data", MapWithAIPlugin.NAME), realBounds.size());
- try {
- final var urls = new ArrayList<>(MapWithAIPreferenceHelper.getMapWithAIUrl());
- final var downloadedDataSets = new ArrayList>(
- realBounds.size() * urls.size());
- for (var bound : realBounds) {
- for (var url : urls) {
- if (url.getUrl() != null && !Utils.isStripEmpty(url.getUrl())) {
- final var ds = download(monitor, bound, url, maximumDimensions);
- downloadedDataSets.add(ds);
- MapWithAIDataUtils.getForkJoinPool().execute(ds);
- }
- }
- }
- mergeDataSets(dataSet, downloadedDataSets);
- } finally {
- monitor.finishTask();
- monitor.close();
- }
- }
- } else {
- final var noUrls = GuiHelper.runInEDTAndWaitAndReturn(
- () -> MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty() ? new Notification(tr(
- "There are no defined URLs. Attempting to add the appropriate servers.\nPlease try again."))
- : new Notification(tr("No URLS are enabled")));
- Objects.requireNonNull(noUrls);
- noUrls.setDuration(Notification.TIME_DEFAULT);
- noUrls.setIcon(JOptionPane.INFORMATION_MESSAGE);
- noUrls.setHelpTopic(ht("Plugin/MapWithAI#Preferences"));
- GuiHelper.runInEDT(noUrls::show);
- if (MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty()
- && MapWithAILayerInfo.getInstance().getDefaultLayers().isEmpty()) {
- MapWithAILayerInfo.getInstance().loadDefaults(true, MapWithAIDataUtils.getForkJoinPool(), false,
- () -> Logging.info("MapWithAI Sources: Initialized sources"));
- }
- }
- return dataSet;
- }
-
- /**
- * Download an area
- *
- * @param monitor The monitor to update
- * @param bound The bounds that are being downloading
- * @param mapWithAIInfo The source of the data
- * @param maximumDimensions The maximum dimensions to download
- * @return A future that will have downloaded the data
- */
- public static ForkJoinTask download(ProgressMonitor monitor, Bounds bound, MapWithAIInfo mapWithAIInfo,
- int maximumDimensions) {
- return ForkJoinTask.adapt(() -> {
- final var downloader = new BoundingBoxMapWithAIDownloader(bound, mapWithAIInfo,
- DetectTaskingManagerUtils.hasTaskingManagerLayer());
- try {
- return downloader.parseOsm(monitor.createSubTaskMonitor(1, false));
- } catch (OsmTransferException e) {
- if (e.getCause() instanceof SocketTimeoutException && maximumDimensions > MAXIMUM_SIDE_DIMENSIONS / 10
- && maximumDimensions / 2f > 0.5) {
- return getData(Collections.singleton(bound), maximumDimensions / 2);
- }
- throw e;
- }
- });
- }
-
- /**
- * Merge datasets
- *
- * @param original The original dataset
- * @param dataSetsToMerge The datasets to merge (futures)
- */
- private static void mergeDataSets(final DataSet original, final List> dataSetsToMerge) {
- for (var ds : dataSetsToMerge) {
- try {
- original.mergeFrom(ds.join());
- } catch (RuntimeException e) {
- final String notificationMessage;
- Throwable cause = e.getCause();
- if (cause != null) {
- while (cause.getCause() != null && RuntimeException.class.equals(cause.getClass())) {
- cause = cause.getCause();
- }
- }
- if (cause instanceof IllegalDataException) {
- notificationMessage = ExceptionUtil.explainException((Exception) cause);
- Logging.trace(e);
- final var notification = new Notification();
- GuiHelper.runInEDT(() -> notification.setContent(notificationMessage));
- GuiHelper.runInEDT(notification::show);
- } else if (cause instanceof OsmTransferException osmTransferException) {
- GuiHelper.runInEDT(() -> ExceptionDialogUtil.explainException(osmTransferException));
- } else {
- throw e;
- }
- }
- }
- }
-
- /**
- * Confirm a large download
- *
- * @param realBounds The list of bounds that will be downloaded
- * @return {@code true} if the user still wants to download data
- */
- private static synchronized boolean confirmBigDownload(List realBounds) {
- final var confirmation = new AtomicBoolean(false);
- // This is not a separate class since we don't want to show multiple
- // confirmation dialogs
- // which is why this method is synchronized.
- GuiHelper.runInEDTAndWait(() -> {
- final var confirmed = ConditionalOptionPaneUtil.showConfirmationDialog(
- MapWithAIPlugin.NAME.concat(".alwaysdownload"), null,
- tr("You are going to make {0} requests to the MapWithAI server. This may take some time. Continue?",
- realBounds.size()),
- null, JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, JOptionPane.YES_OPTION);
- confirmation.set(confirmed);
- });
- return confirmation.get();
- }
-
- /**
- * Get a ForkJoinPool that is safe for use in Webstart
- *
- * @return The {@link ForkJoinPool} for MapWithAI use.
- */
- public static ForkJoinPool getForkJoinPool() {
- if (requiresForkJoinPool == null) {
- requiresForkJoinPool = Utils.isRunningWebStart() || System.getSecurityManager() != null;
- }
- if (requiresForkJoinPool) {
- synchronized (MapWithAIDataUtils.class) {
- if (Objects.isNull(forkJoinPool) || forkJoinPool.isShutdown()) {
- forkJoinPool = Utils.newForkJoinPool(MapWithAIPlugin.NAME.concat(".forkjoinpoolthreads"),
- MapWithAIPlugin.NAME, Thread.NORM_PRIORITY);
- }
- }
- return forkJoinPool;
- }
- return ForkJoinPool.commonPool();
- }
-
- /**
- * Get the height of a bounds
- *
- * @param bounds The bounds with lat/lon information
- * @return The height in meters (see {@link LatLon#greatCircleDistance})
- */
- public static double getHeight(Bounds bounds) {
- final var topRight = bounds.getMax();
- final var bottomLeft = bounds.getMin();
- final double minx = bottomLeft.getX();
- final double maxY = topRight.getY();
- final var topLeft = new LatLon(maxY, minx);
- return bottomLeft.greatCircleDistance((ILatLon) topLeft);
- }
-
- /**
- * Get the first {@link MapWithAILayer} that we can find.
- *
- * @param create true if we want to create a new layer
- * @return A MapWithAILayer, or a new MapWithAILayer if none exist. May return
- * {@code null} if {@code create} is {@code false}.
- */
- public static MapWithAILayer getLayer(boolean create) {
- final var mapWithAILayers = MainApplication.getLayerManager().getLayersOfType(MapWithAILayer.class);
- MapWithAILayer layer = null;
- synchronized (LAYER_LOCK) {
- if (mapWithAILayers.isEmpty() && create) {
- layer = new MapWithAILayer(new DataSet(), MapWithAIPlugin.NAME, null);
- } else if (!mapWithAILayers.isEmpty()) {
- layer = mapWithAILayers.get(0);
- }
- }
-
- final var tLayer = layer;
- if (!MainApplication.getLayerManager().getLayers().contains(tLayer) && create) {
- GuiHelper.runInEDTAndWait(() -> MainApplication.getLayerManager().addLayer(tLayer));
- }
-
- return layer;
- }
-
- /**
- * Get data for a {@link MapWithAILayer}
- *
- * @param layer The {@link MapWithAILayer} to add data to
- * @return true if data was downloaded
- */
- public static boolean getMapWithAIData(MapWithAILayer layer) {
- final var osmLayers = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
- .filter(not(MapWithAILayer.class::isInstance)).toList();
- var gotData = false;
- for (final var osmLayer : osmLayers) {
- if (!osmLayer.isLocked() && getMapWithAIData(layer, osmLayer)) {
- gotData = true;
- }
- }
- return gotData;
- }
-
- /**
- * Get the data for MapWithAI
- *
- * @param layer A pre-existing {@link MapWithAILayer}
- * @param osmLayer The osm datalayer with a set of bounds
- * @return true if data was downloaded
- */
- public static boolean getMapWithAIData(MapWithAILayer layer, OsmDataLayer osmLayer) {
- return getMapWithAIData(layer, osmLayer.getDataSet().getDataSourceBounds());
- }
-
- /**
- * Get the data for MapWithAI
- *
- * @param layer A pre-existing {@link MapWithAILayer}
- * @param bounds The bounds to get the data in
- * @return true if data was downloaded
- */
- public static boolean getMapWithAIData(MapWithAILayer layer, Bounds... bounds) {
- return getMapWithAIData(layer, Arrays.asList(bounds));
- }
-
- /**
- * Get the data for MapWithAI
- *
- * @param layer A pre-existing {@link MapWithAILayer}
- * @param bounds The bounds to get the data in
- * @return true if data was downloaded
- */
- public static boolean getMapWithAIData(MapWithAILayer layer, Collection bounds) {
- final var mapWithAISet = layer.getDataSet();
- final var area = mapWithAISet.getDataSourceArea();
- final var toDownload = area == null ? new ArrayList<>(bounds)
- : bounds.stream().filter(Objects::nonNull).filter(tBounds -> !area.contains(tBounds.asRect())).toList();
- if (!toDownload.isEmpty()) {
- getForkJoinPool().execute(() -> {
- final var newData = getData(toDownload, MAXIMUM_SIDE_DIMENSIONS);
- final var lock = layer.getLock();
- lock.lock();
- try {
- mapWithAISet.update(() -> mapWithAISet.mergeFrom(newData));
- GetDataRunnable.cleanup(mapWithAISet, null, null);
- } finally {
- lock.unlock();
- }
- toDownload.forEach(layer::onPostDownloadFromServer);
- });
- }
- return !toDownload.isEmpty();
- }
-
- /**
- * Get the width of a bounds
- *
- * @param bounds The bounds to get the width of
- * @return See {@link LatLon#greatCircleDistance}
- */
- public static double getWidth(Bounds bounds) {
- // Lat is y, Lon is x
- final var bottomLeft = bounds.getMin();
- final var topRight = bounds.getMax();
- final double minX = bottomLeft.getX();
- final double maxX = topRight.getX();
- final double minY = bottomLeft.getY();
- final double maxY = topRight.getY();
- final var bottomRight = new LatLon(minY, maxX);
- final var topLeft = new LatLon(maxY, minX);
- return Math.max(bottomLeft.greatCircleDistance((ILatLon) bottomRight),
- topLeft.greatCircleDistance((ILatLon) topRight));
- }
-
- /**
- * Reduce a bound to the specified dimensions, returning a list of bounds.
- *
- * @param bound The bound to reduce to a set maximum dimension
- * @param maximumDimensions The maximum side dimensions of the bound
- * @return A list of Bounds that have a dimension no more than
- * {@code maximumDimensions}
- */
- public static List reduceBoundSize(Bounds bound, int maximumDimensions) {
- final var returnBounds = new ArrayList();
- final double width = getWidth(bound);
- final double height = getHeight(bound);
- final double widthDivisions = width / maximumDimensions;
- final double heightDivisions = height / maximumDimensions;
- final int widthSplits = (int) widthDivisions + ((widthDivisions - Math.floor(widthDivisions)) > 0 ? 1 : 0);
- final int heightSplits = (int) heightDivisions + ((heightDivisions - Math.floor(heightDivisions)) > 0 ? 1 : 0);
-
- final double newMinWidths = Math.abs(bound.getMaxLon() - bound.getMinLon()) / widthSplits;
- final double newMinHeights = Math.abs(bound.getMaxLat() - bound.getMinLat()) / heightSplits;
-
- final double minx = bound.getMinLon();
- final double miny = bound.getMinLat();
- for (var x = 1; x <= widthSplits; x++) {
- for (var y = 1; y <= heightSplits; y++) {
- final var lowerLeft = new LatLon(miny + (newMinHeights * (y - 1)), minx + (newMinWidths * (x - 1)));
- final var upperRight = new LatLon(miny + (newMinHeights * y), minx + (newMinWidths * x));
- returnBounds.add(new Bounds(lowerLeft, upperRight));
- }
- }
- return returnBounds.stream().distinct().collect(Collectors.toList());
- }
-
- /**
- * Reduce a list of bounds to {@link MapWithAIDataUtils#MAXIMUM_SIDE_DIMENSIONS}
- *
- * @param bounds The bounds to reduce to a set maximum dimension
- * @return A list of Bounds that have a dimension no more than
- * {@link MapWithAIDataUtils#MAXIMUM_SIDE_DIMENSIONS}
- */
- public static List reduceBoundSize(List bounds) {
- return reduceBoundSize(bounds, MAXIMUM_SIDE_DIMENSIONS);
- }
-
- /**
- * Reduce a list of bounds to a specified size
- *
- * @param bounds The bounds to reduce to a set maximum dimension
- * @param maximumDimensions The maximum width/height dimensions
- * @return A list of Bounds that have a dimension no more than the
- * {@code maximumDimensions}
- */
- public static List reduceBoundSize(List bounds, int maximumDimensions) {
- final var returnBounds = new ArrayList(bounds.size());
- bounds.forEach(bound -> returnBounds.addAll(reduceBoundSize(bound, maximumDimensions)));
- return returnBounds.stream().distinct().toList();
- }
-
- /**
- * Remove primitives and their children from a dataset.
- *
- * @param primitives The primitives to remove
- */
- public static void removePrimitivesFromDataSet(Collection primitives) {
- for (final var primitive : primitives) {
- if (primitive instanceof Relation relation) {
- removePrimitivesFromDataSet(relation.getMemberPrimitives());
- } else if (primitive instanceof Way way) {
- for (final var node : way.getNodes()) {
- final var ds = node.getDataSet();
- if (ds != null) {
- ds.removePrimitive(node);
- }
- }
- }
- final var ds = primitive.getDataSet();
- if (ds != null) {
- ds.removePrimitive(primitive);
- }
- }
- }
-
- /**
- * Get the number of whole objects added from the MapWithAI layer. A whole
- * object is an object with tags or not a member of another object.
- *
- * @return The number of objects added from the MapWithAI data layer
- */
- public static Long getAddedObjects() {
- return Optional
- .ofNullable(GuiHelper.runInEDTAndWaitAndReturn(() -> UndoRedoHandler.getInstance().getUndoCommands()))
- .map(commands -> commands.stream().filter(MapWithAIAddCommand.class::isInstance)
- .map(MapWithAIAddCommand.class::cast).mapToLong(MapWithAIAddCommand::getAddedObjects).sum())
- .orElse(0L);
- }
-
- /**
- * Get source tags for objects added from the MapWithAI data layer
- *
- * @return The source tags for Objects added from the MapWithAI data layer
- */
- public static List getAddedObjectsSource() {
- return Optional
- .ofNullable(GuiHelper.runInEDTAndWaitAndReturn(() -> UndoRedoHandler.getInstance().getUndoCommands()))
- .map(commands -> commands.stream().filter(MapWithAIAddCommand.class::isInstance)
- .map(MapWithAIAddCommand.class::cast).flatMap(com -> com.getSourceTags().stream()).distinct()
- .collect(Collectors.toList()))
- .orElseGet(Collections::emptyList);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAILayer.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAILayer.java
deleted file mode 100644
index f6c83a78..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAILayer.java
+++ /dev/null
@@ -1,446 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import javax.swing.AbstractAction;
-import javax.swing.Action;
-import javax.swing.Icon;
-import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.SwingConstants;
-
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.io.File;
-import java.io.IOException;
-import java.io.Serial;
-import java.io.Serializable;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.TreeSet;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.actions.ExpertToggleAction;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.DataSource;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.DownloadPolicy;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.UploadPolicy;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.dialogs.layer.DuplicateAction;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent;
-import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
-import org.openstreetmap.josm.gui.mappaint.StyleSource;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.gui.widgets.HtmlPanel;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.plugins.mapwithai.tools.BlacklistUtils;
-import org.openstreetmap.josm.plugins.mapwithai.tools.MapPaintUtils;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangeEvent;
-import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
-import org.openstreetmap.josm.tools.GBC;
-import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * This layer shows MapWithAI data. For various reasons, we currently only allow
- * one to be created, although this may change.
- *
- * @author Taylor Smock
- *
- */
-public class MapWithAILayer extends OsmDataLayer implements ActiveLayerChangeListener, PreferenceChangedListener {
- private static final Collection COMPACT = Collections.singleton("esri");
- private Integer maximumAddition;
- private MapWithAIInfo url;
- private Boolean switchLayers;
- private boolean continuousDownload = true;
- private final Lock lock;
- private final HashSet downloadedInfo = new HashSet<>();
-
- /**
- * Create a new MapWithAI layer
- *
- * @param data OSM data from MapWithAI
- * @param name Layer name
- * @param associatedFile an associated file (can be null)
- */
- public MapWithAILayer(DataSet data, String name, File associatedFile) {
- super(data, name, associatedFile);
- data.setUploadPolicy(UploadPolicy.BLOCKED);
- data.setDownloadPolicy(DownloadPolicy.BLOCKED);
- lock = new MapLock();
- MainApplication.getLayerManager().addActiveLayerChangeListener(this);
- new ContinuousDownloadAction(this); // Initialize data source listeners
- Config.getPref().addKeyPreferenceChangeListener("download.mapwithai.data", this);
- }
-
- @Override
- public String getChangesetSourceTag() {
- if (MapWithAIDataUtils.getAddedObjects() > 0) {
- TreeSet sources = MapWithAIDataUtils.getAddedObjectsSource().stream().filter(Objects::nonNull)
- .map(string -> COMPACT.stream().filter(string::contains).findAny().orElse(string))
- .collect(Collectors.toCollection(TreeSet::new));
- sources.add("MapWithAI");
- return String.join("; ", sources);
- }
- return null;
- }
-
- public void setMaximumAddition(Integer max) {
- maximumAddition = max;
- }
-
- public Integer getMaximumAddition() {
- return maximumAddition;
- }
-
- public void setMapWithAIUrl(MapWithAIInfo info) {
- this.url = info;
- }
-
- public MapWithAIInfo getMapWithAIUrl() {
- return url;
- }
-
- public void setSwitchLayers(boolean selected) {
- switchLayers = selected;
- }
-
- public Boolean isSwitchLayers() {
- return switchLayers;
- }
-
- @Override
- public Object getInfoComponent() {
- final Object p = super.getInfoComponent();
- if (p instanceof JPanel panel) {
- if (maximumAddition != null) {
- panel.add(new JLabel(tr("Maximum Additions: {0}", maximumAddition), SwingConstants.CENTER),
- GBC.eop().insets(15, 0, 0, 0));
- }
- if (url != null) {
- panel.add(new JLabel(tr("URL: {0}", url.getUrlExpanded()), SwingConstants.CENTER),
- GBC.eop().insets(15, 0, 0, 0));
- }
- if (switchLayers != null) {
- panel.add(new JLabel(tr("Switch Layers: {0}", switchLayers), SwingConstants.CENTER),
- GBC.eop().insets(15, 0, 0, 0));
- }
- }
- return p;
- }
-
- @Override
- public Action[] getMenuEntries() {
- Collection> forbiddenActions = Arrays.asList(LayerSaveAction.class,
- LayerSaveAsAction.class, DuplicateAction.class, LayerGpxExportAction.class,
- ConvertToGpxLayerAction.class);
- final List actions = Arrays.stream(super.getMenuEntries())
- .filter(action -> forbiddenActions.stream().noneMatch(clazz -> clazz.isInstance(action)))
- .collect(Collectors.toCollection(ArrayList::new));
- if (actions.isEmpty()) {
- actions.add(new ContinuousDownloadAction(this));
- } else {
- actions.add(actions.size() - 2, new ContinuousDownloadAction(this));
- }
- return actions.toArray(new Action[0]);
- }
-
- public Lock getLock() {
- return lock;
- }
-
- @Override
- public void preferenceChanged(PreferenceChangeEvent e) {
- if ("download.mapwithai.data".equals(e.getKey())) {
- final Object value = e.getNewValue().getValue();
- if (value instanceof Boolean bool) {
- this.continuousDownload = bool;
- } else if (value instanceof String str) {
- this.continuousDownload = Boolean.parseBoolean(str);
- } else {
- this.continuousDownload = false;
- }
- }
- }
-
- private class MapLock extends ReentrantLock {
- @Serial
- private static final long serialVersionUID = 5441350396443132682L;
- private boolean dataSetLocked;
-
- public MapLock() {
- super();
- // Do nothing
- }
-
- @Override
- public void lock() {
- super.lock();
- dataSetLocked = getDataSet().isLocked();
- if (dataSetLocked) {
- getDataSet().unlock();
- }
- }
-
- @Override
- public void unlock() {
- super.unlock();
- if (dataSetLocked) {
- getDataSet().lock();
- }
- }
- }
-
- @Override
- public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
- if (checkIfToggleLayer()) {
- final StyleSource style = MapPaintUtils.getMapWithAIPaintStyle();
- if (style.active != this.equals(MainApplication.getLayerManager().getActiveLayer())) {
- MapPaintStyles.toggleStyleActive(MapPaintStyles.getStyles().getStyleSources().indexOf(style));
- }
- }
- }
-
- private static boolean checkIfToggleLayer() {
- final List keys = Config.getPref().getKeySet().parallelStream()
- .filter(string -> string.contains(MapWithAIPlugin.NAME) && string.contains("boolean:toggle_with_layer"))
- .toList();
- boolean toggle = false;
- if (keys.size() == 1) {
- toggle = Config.getPref().getBoolean(keys.get(0), false);
- }
- return toggle;
- }
-
- @Override
- public synchronized void destroy() {
- Config.getPref().removeKeyPreferenceChangeListener("download.mapwithai.data", this);
- super.destroy();
- MainApplication.getLayerManager().removeActiveLayerChangeListener(this);
- }
-
- @Override
- public Icon getIcon() {
- return ImageProvider.getIfAvailable("mapwithai") == null ? super.getIcon()
- : ImageProvider.get("mapwithai", ImageProvider.ImageSizes.LAYER);
- }
-
- /**
- * Call after download from server
- *
- * @param bounds The newly added bounds
- */
- public void onPostDownloadFromServer(Bounds bounds) {
- super.onPostDownloadFromServer();
- GetDataRunnable.cleanup(getDataSet(), bounds, null);
- if (!this.data.getDataSourceBounds().contains(bounds)) {
- this.data.addDataSource(new DataSource(bounds, null));
- }
- }
-
- @Override
- public void selectionChanged(SelectionChangeEvent event) {
- if (BlacklistUtils.isBlacklisted()) {
- if (!event.getSelection().isEmpty()) {
- GuiHelper.runInEDT(() -> getDataSet().setSelected(Collections.emptySet()));
- createBadDataNotification();
- }
- return;
- }
- super.selectionChanged(event);
- final int maximumAdditionSelection = MapWithAIPreferenceHelper.getMaximumAddition();
- if ((event.getSelection().size() - event.getOldSelection().size() > 1
- || maximumAdditionSelection < event.getSelection().size())
- && (MapWithAIPreferenceHelper.getMaximumAddition() != 0 || !ExpertToggleAction.isExpert())) {
- Collection selection;
- final Collection oldWays = Utils.filteredCollection(event.getOldSelection(), Way.class);
- if (Utils.filteredCollection(event.getSelection(), Node.class).stream()
- .filter(n -> !event.getOldSelection().contains(n))
- .allMatch(n -> oldWays.stream().anyMatch(w -> w.containsNode(n)))) {
- selection = event.getSelection();
- } else {
- OsmComparator comparator = new OsmComparator(event.getOldSelection());
- selection = event.getSelection().stream().distinct().sorted(comparator).limit(maximumAdditionSelection)
- .limit(event.getOldSelection().size() + Math.max(1L, maximumAdditionSelection / 10L))
- .collect(Collectors.toList());
- }
- GuiHelper.runInEDT(() -> getDataSet().setSelected(selection));
- }
- }
-
- /**
- * Create a notification for plugin versions that create bad data.
- */
- public static void createBadDataNotification() {
- Notification badData = new Notification();
- badData.setIcon(JOptionPane.ERROR_MESSAGE);
- badData.setDuration(Notification.TIME_LONG);
- HtmlPanel panel = new HtmlPanel();
- StringBuilder message = new StringBuilder()
- .append(tr("This version of the MapWithAI plugin is known to create bad data.")).append(" ")
- .append(tr("Please update plugins and/or JOSM."));
- if (BlacklistUtils.isOffline()) {
- message.append(" ").append(tr("This message may occur when JOSM is offline."));
- }
- panel.setText(message.toString());
- badData.setContent(panel);
- MapWithAILayer layer = MapWithAIDataUtils.getLayer(false);
- if (layer != null) {
- layer.setMaximumAddition(0);
- }
- GuiHelper.runInEDT(badData::show);
- }
-
- /**
- * Compare OsmPrimitives in a custom manner
- */
- private record OsmComparator(
- Collection previousSelection) implements Comparator, Serializable {
-
- @Override
- public int compare(OsmPrimitive o1, OsmPrimitive o2) {
- if (previousSelection.contains(o1) == previousSelection.contains(o2)) {
- if (o1.isTagged() == o2.isTagged()) {
- return o1.compareTo(o2);
- } else if (o1.isTagged()) {
- return -1;
- }
- return 1;
- }
- if (previousSelection.contains(o1)) {
- return -1;
- }
- return 1;
- }
-
- }
-
- /**
- * Check if we want to download data continuously
- *
- * @return {@code true} indicates that we should attempt to keep it in sync with
- * the data layer(s)
- */
- public boolean downloadContinuous() {
- return continuousDownload;
- }
-
- /**
- * Allow continuous download of data (for the layer that MapWithAI is clamped
- * to).
- *
- * @author Taylor Smock
- */
- public static class ContinuousDownloadAction extends AbstractAction implements LayerAction {
- @Serial
- private static final long serialVersionUID = -3528632887550700527L;
- private final transient MapWithAILayer layer;
-
- /**
- * Create a new continuous download toggle
- *
- * @param layer the layer to toggle continuous download for
- */
- public ContinuousDownloadAction(MapWithAILayer layer) {
- super(tr("Continuous download"));
- new ImageProvider("download").getResource().attachImageIcon(this, true);
- this.layer = layer;
- updateListeners();
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- layer.continuousDownload = !layer.continuousDownload;
- updateListeners();
- }
-
- void updateListeners() {
- if (layer.continuousDownload) {
- for (OsmDataLayer data : MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class)) {
- if (!(data instanceof MapWithAILayer)) {
- new DownloadListener(data.getDataSet());
- }
- }
- } else {
- DownloadListener.destroyAll();
- }
- }
-
- @Override
- public boolean supportLayers(List layers) {
- return layers.stream().allMatch(MapWithAILayer.class::isInstance);
- }
-
- @Override
- public Component createMenuComponent() {
- JCheckBoxMenuItem item = new JCheckBoxMenuItem(this);
- item.setSelected(layer.continuousDownload);
- return item;
- }
-
- }
-
- /**
- * Check if the layer has downloaded a specific data type
- *
- * @param info The info to check
- * @return {@code true} if the info has been added to the layer
- */
- public boolean hasDownloaded(MapWithAIInfo info) {
- return downloadedInfo.contains(info);
- }
-
- /**
- * Indicate an info has been downloaded in this layer
- *
- * @param info The info that has been downloaded
- */
- public void addDownloadedInfo(MapWithAIInfo info) {
- downloadedInfo.add(info);
- }
-
- /**
- * Get the info that has been downloaded into this layer
- *
- * @return An unmodifiable collection of the downloaded info
- */
- public Collection getDownloadedInfo() {
- return Collections.unmodifiableCollection(downloadedInfo);
- }
-
- @Override
- public boolean autosave(File file) throws IOException {
- // Consider a deletion a "successful" save.
- return Files.deleteIfExists(file.toPath());
- }
-
- @Override
- public boolean isMergable(final Layer other) {
- // Don't allow this layer to be merged down
- return other instanceof MapWithAILayer;
- }
-
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIMoveAction.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIMoveAction.java
deleted file mode 100644
index aad4c95c..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIMoveAction.java
+++ /dev/null
@@ -1,152 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import javax.swing.JOptionPane;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.InputEvent;
-import java.awt.event.KeyEvent;
-import java.util.Collection;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.actions.ExpertToggleAction;
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.data.UndoRedoHandler;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.Notification;
-import org.openstreetmap.josm.gui.layer.Layer;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.commands.MapWithAIAddCommand;
-import org.openstreetmap.josm.plugins.mapwithai.tools.BlacklistUtils;
-import org.openstreetmap.josm.tools.Shortcut;
-
-/**
- * Move data between the MapWithAI layer and an OSM data layer
- */
-public class MapWithAIMoveAction extends JosmAction {
- /** UID for abstract action */
- private static final long serialVersionUID = 319374598;
-
- /** The maximum number of objects is this times the maximum add */
- public static final long MAX_ADD_MULTIPLIER = 10;
-
- /**
- * Create a new action
- */
- public MapWithAIMoveAction() {
- super(tr("{0}: Add selected data", MapWithAIPlugin.NAME), "mapwithai",
- tr("Add data from {0}", MapWithAIPlugin.NAME), obtainShortcut(), true, "mapwithai:movedata", true);
- setHelpId(ht("Plugin/MapWithAI#BasicUsage"));
- }
-
- /**
- * @return The default shortcut, if available, or an alternate shortcut that
- * makes sense otherwise
- */
- private static Shortcut obtainShortcut() {
- int key = KeyEvent.VK_A;
- int modifier = Shortcut.SHIFT;
- final String shortText = "data:mapwithaiadd";
- final Optional shortCut = Shortcut.findShortcut(key, InputEvent.SHIFT_DOWN_MASK); // Shortcut.SHIFT
- // maps to
- // KeyEvent.SHIFT_DOWN_MASK
- if (shortCut.isPresent() && !shortText.equals(shortCut.get().getShortText())) {
- key = KeyEvent.VK_C;
- modifier = Shortcut.ALT;
- }
- return Shortcut.registerShortcut(shortText, tr("{0}: {1}", MapWithAIPlugin.NAME, tr("Add selected data")), key,
- modifier);
- }
-
- @Override
- public void actionPerformed(ActionEvent event) {
- if (BlacklistUtils.isBlacklisted()) {
- MapWithAILayer.createBadDataNotification();
- return;
- }
- for (final MapWithAILayer mapWithAI : MainApplication.getLayerManager().getLayersOfType(MapWithAILayer.class)) {
- final DataSet ds = mapWithAI.getDataSet();
- final int maxAddition = MapWithAIPreferenceHelper.getMaximumAddition();
- final List nodes = ds.getSelectedNodes().stream().filter(node -> !node.getReferrers().isEmpty())
- .collect(Collectors.toList());
- ds.clearSelection(nodes);
- nodes.stream().map(Node::getReferrers).forEach(ds::addSelected);
- final Collection selected = limitCollection(ds, maxAddition);
- final OsmDataLayer editLayer = getOsmDataLayer();
- if (editLayer != null && !selected.isEmpty()
- && (MapWithAIDataUtils.getAddedObjects() < maxAddition * MAX_ADD_MULTIPLIER
- || (maxAddition == 0 && ExpertToggleAction.isExpert()))) {
- final MapWithAIAddCommand command = new MapWithAIAddCommand(mapWithAI, editLayer, selected);
- GuiHelper.runInEDTAndWait(() -> UndoRedoHandler.getInstance().add(command));
- if (MapWithAIPreferenceHelper.isSwitchLayers()) {
- MainApplication.getLayerManager().setActiveLayer(editLayer);
- }
- } else if (MapWithAIDataUtils.getAddedObjects() >= maxAddition * MAX_ADD_MULTIPLIER) {
- createTooManyAdditionsNotification(maxAddition);
- }
- }
- }
-
- private static void createTooManyAdditionsNotification(int maxAddition) {
- Notification tooMany = new Notification();
- tooMany.setIcon(JOptionPane.WARNING_MESSAGE);
- tooMany.setDuration(Notification.TIME_DEFAULT);
- tooMany.setContent(
- tr("There is a soft cap of {0} objects before uploading. Please verify everything before uploading.",
- maxAddition * MAX_ADD_MULTIPLIER));
- tooMany.show();
- }
-
- private static Collection limitCollection(DataSet ds, int maxSize) {
- return (maxSize > 0 || !ExpertToggleAction.isExpert())
- ? ds.getSelected().stream().limit(maxSize).collect(Collectors.toList())
- : ds.getSelected();
- }
-
- /**
- * Get the OSM Data Layer to add MapWithAI data to
- *
- * @return An OSM data layer that data can be added to
- */
- public static OsmDataLayer getOsmDataLayer() {
- return MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
- .filter(OsmDataLayer::isVisible).filter(OsmDataLayer::isUploadable)
- .filter(osmLayer -> !osmLayer.isLocked() && osmLayer.getClass().equals(OsmDataLayer.class)).findFirst()
- .orElse(null);
- }
-
- @Override
- protected void updateEnabledState() {
- setEnabled(checkIfActionEnabled());
- }
-
- @Override
- protected void updateEnabledState(Collection extends OsmPrimitive> selection) {
- if ((selection == null) || selection.isEmpty()) {
- setEnabled(false);
- } else {
- setEnabled(checkIfActionEnabled());
- }
- }
-
- private boolean checkIfActionEnabled() {
- boolean returnValue = false;
- final Layer active = getLayerManager().getActiveLayer();
- if (active instanceof MapWithAILayer) {
- final MapWithAILayer mapWithAILayer = (MapWithAILayer) active;
- final Collection selection = mapWithAILayer.getDataSet().getAllSelected();
- returnValue = (selection != null) && !selection.isEmpty();
- }
- return returnValue;
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIObject.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIObject.java
deleted file mode 100644
index 2f47b886..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIObject.java
+++ /dev/null
@@ -1,94 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.Color;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.openstreetmap.josm.data.UndoRedoHandler;
-import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
-import org.openstreetmap.josm.gui.MapStatus;
-import org.openstreetmap.josm.gui.widgets.JosmTextField;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.tools.Destroyable;
-import org.openstreetmap.josm.tools.GBC;
-
-/**
- * Show the number of MapWithAI objects that have been added since the last time
- * the command stack was cleared. The stack is usually cleared on upload.
- *
- * @author Taylor Smock
- *
- */
-public class MapWithAIObject implements CommandQueueListener, Destroyable {
- private final JosmTextField mapWithAIObjects;
- private final List statusLines;
-
- /**
- * Create a new status line for added objects
- */
- public MapWithAIObject() {
- mapWithAIObjects = new JosmTextField(null, null, "MapWithAI Objects Added: 1000".length() - 10, false);
- mapWithAIObjects.setBackground(MapStatus.PROP_BACKGROUND_COLOR.get());
- mapWithAIObjects.setEditable(false);
- statusLines = new ArrayList<>();
- UndoRedoHandler.getInstance().addCommandQueueListener(this);
- setText();
- }
-
- /**
- * Adds a new status line to the map status
- *
- * @param mapStatus The status bar to add a count to
- */
- public void addMapStatus(MapStatus mapStatus) {
- statusLines.add(mapStatus);
- mapStatus.add(mapWithAIObjects, GBC.std().insets(3, 0, 0, 0), mapStatus.getComponentCount() - 2);
- }
-
- /**
- * Removes a status line from the map status
- *
- * @param mapStatus The status bar to remove a count from
- */
- public void removeMapStatus(MapStatus mapStatus) {
- mapStatus.remove(mapWithAIObjects);
- statusLines.remove(mapStatus);
- }
-
- @Override
- public void commandChanged(int queueSize, int redoSize) {
- setText();
- }
-
- private void setText() {
- final long addedObjects = MapWithAIDataUtils.getAddedObjects();
- if (addedObjects == 0L) {
- mapWithAIObjects.setVisible(false);
- mapWithAIObjects.setVisible(true);
- mapWithAIObjects.setText(tr("{0} Objects Added: {1}", MapWithAIPlugin.NAME, addedObjects));
- } else {
- mapWithAIObjects.setVisible(true);
- mapWithAIObjects.setText(tr("{0} Objects Added: {1}", MapWithAIPlugin.NAME, addedObjects));
- }
- final int maxAdd = MapWithAIPreferenceHelper.getMaximumAddition();
- if (addedObjects == 0) {
- mapWithAIObjects.setBackground(MapStatus.PROP_BACKGROUND_COLOR.get());
- } else if (addedObjects < maxAdd) {
- mapWithAIObjects.setBackground(Color.GREEN);
- } else if (addedObjects < 10L * maxAdd) {
- mapWithAIObjects.setBackground(Color.YELLOW);
- } else {
- mapWithAIObjects.setBackground(Color.RED);
- }
- }
-
- @Override
- public void destroy() {
- statusLines.forEach(mapStatus -> mapStatus.remove(mapWithAIObjects));
- statusLines.clear();
- UndoRedoHandler.getInstance().removeCommandQueueListener(this);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIPreferenceHelper.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIPreferenceHelper.java
deleted file mode 100644
index 2e079202..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIPreferenceHelper.java
+++ /dev/null
@@ -1,201 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import org.openstreetmap.josm.data.osm.Tag;
-import org.openstreetmap.josm.data.preferences.BooleanProperty;
-import org.openstreetmap.josm.data.preferences.CachingProperty;
-import org.openstreetmap.josm.data.preferences.DoubleProperty;
-import org.openstreetmap.josm.data.preferences.IntegerProperty;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAILayerInfo;
-import org.openstreetmap.josm.spi.preferences.Config;
-
-/**
- * Helper for MapWithAI preferences
- */
-public final class MapWithAIPreferenceHelper {
- private static final String AUTOSWITCHLAYERS = MapWithAIPlugin.NAME.concat(".autoswitchlayers");
- private static final String MERGEBUILDINGADDRESSES = MapWithAIPlugin.NAME.concat(".mergebuildingaddresses");
- private static final String MAXIMUMSELECTION = MapWithAIPlugin.NAME.concat(".maximumselection");
- private static final CachingProperty PROPERTY_DUPLICATE_NODE_DISTANCE = new DoubleProperty(
- MapWithAIPlugin.NAME.concat(".duplicatenodedistance"), 0.6).cached();
- private static final IntegerProperty PROPERTY_MAXIMUM_SELECTION = new IntegerProperty(MAXIMUMSELECTION, 100);
- private static final BooleanProperty PROPERTY_MERGEBUILDINGADDRESSES = new BooleanProperty(MERGEBUILDINGADDRESSES,
- true);
- private static final BooleanProperty PROPERTY_AUTOSWITCHLAYERS = new BooleanProperty(AUTOSWITCHLAYERS, true);
-
- private MapWithAIPreferenceHelper() {
- // Hide the constructor
- }
-
- /**
- * The default maximum number of objects to add
- *
- * @return {@link MapWithAIPreferenceHelper#PROPERTY_MAXIMUM_SELECTION} default
- */
- public static int getDefaultMaximumAddition() {
- return PROPERTY_MAXIMUM_SELECTION.getDefaultValue();
- }
-
- /**
- * Get the current MapWithAI urls
- *
- * @return A list of enabled MapWithAI urls (maps have source, parameters,
- * enabled, and the url)
- */
- public static Collection getMapWithAIUrl() {
- MapWithAILayer layer = MapWithAIDataUtils.getLayer(false);
- if (layer != null) {
- if (!layer.getDownloadedInfo().isEmpty()) {
- return layer.getDownloadedInfo();
- } else if (layer.getMapWithAIUrl() != null) {
- return Collections.singleton(layer.getMapWithAIUrl());
- }
- }
- return MapWithAILayerInfo.getInstance().getLayers();
- }
-
- /**
- * Get the maximum number of objects that can be added at one time
- *
- * @return The maximum selection. If 0, allow any number.
- */
- public static int getMaximumAddition() {
- final MapWithAILayer mapWithAILayer = MapWithAIDataUtils.getLayer(false);
- Integer defaultReturn = PROPERTY_MAXIMUM_SELECTION.get();
- if ((mapWithAILayer != null) && (mapWithAILayer.getMaximumAddition() != null)) {
- defaultReturn = mapWithAILayer.getMaximumAddition();
- }
- return defaultReturn > getDefaultMaximumAddition() * 10 ? getDefaultMaximumAddition() * 10 : defaultReturn;
- }
-
- /**
- * Check if the user wants to merge buildings and addresses
- *
- * @return {@code true} if we want to automatically merge buildings with
- * pre-existing addresses
- */
- public static boolean isMergeBuildingAddress() {
- return PROPERTY_MERGEBUILDINGADDRESSES.get();
- }
-
- /**
- * Check if the user wants to switch layers automatically after adding data.
- *
- * @return {@code true} if we want to automatically switch layers
- */
- public static boolean isSwitchLayers() {
- final MapWithAILayer layer = MapWithAIDataUtils.getLayer(false);
- boolean returnBoolean = PROPERTY_AUTOSWITCHLAYERS.get();
- if ((layer != null) && (layer.isSwitchLayers() != null)) {
- returnBoolean = layer.isSwitchLayers();
- }
- return returnBoolean;
- }
-
- /**
- * Add a MapWithAI url. If both boolean parameters are false, nothing happens.
- *
- * @param info The info to add
- * @param enabled If it should be enabled
- * @param permanent If it should be added permanently
- */
- public static void setMapWithAIUrl(MapWithAIInfo info, boolean enabled, boolean permanent) {
- if (permanent && enabled) {
- MapWithAILayerInfo.getInstance().add(info);
- MapWithAILayerInfo.getInstance().save();
- } else if (enabled && MapWithAIDataUtils.getLayer(false) != null) {
- MapWithAIDataUtils.getLayer(false).setMapWithAIUrl(info);
- }
- }
-
- /**
- * Set the maximum number of objects that can be added at one time.
- *
- * @param max The maximum number of objects to select (0 allows any number
- * to be selected).
- * @param permanent {@code true} if we want the setting to persist between
- * sessions
- */
- public static void setMaximumAddition(int max, boolean permanent) {
- final MapWithAILayer mapWithAILayer = MapWithAIDataUtils.getLayer(false);
- if (permanent) {
- if (getDefaultMaximumAddition() == max) {
- PROPERTY_MAXIMUM_SELECTION.remove();
- } else {
- PROPERTY_MAXIMUM_SELECTION.put(max);
- }
- } else if (mapWithAILayer != null) {
- mapWithAILayer.setMaximumAddition(max);
- }
- }
-
- /**
- * Set whether or not a we switch from the MapWithAI layer to an OSM data layer
- *
- * @param selected true if we are going to switch layers
- * @param permanent {@code true} if we want the setting to persist between
- * sessions
- */
- public static void setMergeBuildingAddress(boolean selected, boolean permanent) {
- if (permanent) {
- PROPERTY_MERGEBUILDINGADDRESSES.put(selected);
- }
- }
-
- /**
- * Set whether or not a we switch from the MapWithAI layer to an OSM data layer
- *
- * @param selected true if we are going to switch layers
- * @param permanent {@code true} if we want the setting to persist between
- * sessions
- */
- public static void setSwitchLayers(boolean selected, boolean permanent) {
- final MapWithAILayer layer = MapWithAIDataUtils.getLayer(false);
- if (permanent) {
- PROPERTY_AUTOSWITCHLAYERS.put(selected);
- } else if (layer != null) {
- layer.setSwitchLayers(selected);
- }
- }
-
- /**
- * Get the maximum distance for a node to be considered a duplicate
- *
- * @return The max distance between nodes for duplicates
- */
- public static double getMaxNodeDistance() {
- return PROPERTY_DUPLICATE_NODE_DISTANCE.get();
- }
-
- /**
- * Get the tags to replace
- *
- * @return A map of tags to replacement tags (use {@link Tag#ofString} to parse)
- */
- public static Map getReplacementTags() {
- final Map defaultMap = Collections.emptyMap();
- final List> listOfMaps = Config.getPref()
- .getListOfMaps(MapWithAIPlugin.NAME.concat(".replacementtags"), Collections.singletonList(defaultMap));
- return listOfMaps.isEmpty() ? defaultMap : listOfMaps.get(0);
- }
-
- /**
- * Set the tags to replace
- *
- * @param tagsToReplace set the tags to replace
- */
- public static void setReplacementTags(Map tagsToReplace) {
- final List> tags = tagsToReplace.isEmpty() ? null
- : Collections.singletonList(new TreeMap<>(tagsToReplace));
- Config.getPref().putListOfMaps(MapWithAIPlugin.NAME.concat(".replacementtags"), tags);
- }
-
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIRemoteControl.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIRemoteControl.java
deleted file mode 100644
index 20888013..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIRemoteControl.java
+++ /dev/null
@@ -1,181 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.layer.GpxLayer;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault;
-import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.MapWithAIInfo;
-
-/**
- * Download MapWithAI data using remote control
- */
-public class MapWithAIRemoteControl extends RequestHandler.RawURLParseRequestHandler {
-
- private static final PermissionPrefWithDefault PERMISSION_PREF_WITH_DEFAULT = new PermissionPrefWithDefault(
- MapWithAIPlugin.NAME.concat(".remote_control"), true, tr("MapWithAI"));
-
- private Bounds download;
- private Bounds crop;
- private Integer maxObj;
- private Boolean switchLayer;
- private String url;
- private String source;
-
- private static final String MAX_OBJ = "max_obj";
- private static final String SWITCH_LAYER = "switch_layer";
- private static final String BBOX = "bbox";
- private static final String CROP_BBOX = "crop_bbox";
- private static final String URL_STRING = "url";
- private static final String SOURCE_STRING = "source";
-
- @Override
- protected void validateRequest() throws RequestHandlerBadRequestException {
- if (args != null) {
- try {
- if (args.containsKey(BBOX)) {
- download = parseBounds(args.get(BBOX));
- }
- if (args.containsKey(CROP_BBOX)) {
- crop = parseBounds(args.get(CROP_BBOX));
- }
- if (args.containsKey(MAX_OBJ)) {
- maxObj = Integer.parseInt(args.get(MAX_OBJ));
- }
- if (args.containsKey(URL_STRING)) {
- final String urlString = args.get(URL_STRING);
- // Ensure the URL_STRING is valid
- url = new URL(urlString).toString();
- }
- if (args.containsKey(SOURCE_STRING)) {
- source = args.get(SOURCE_STRING);
- }
- if (args.containsKey(SWITCH_LAYER)) {
- switchLayer = Boolean.parseBoolean(args.get(SWITCH_LAYER));
- }
- } catch (NumberFormatException e) {
- throw new RequestHandlerBadRequestException("NumberFormatException (" + e.getMessage() + ')', e);
- } catch (MalformedURLException e) {
- throw new RequestHandlerBadRequestException("MalformedURLException: " + e.getMessage(), e);
- }
- }
- }
-
- /**
- * Parse a string of coordinates into bounds
- *
- * @param coordinates The coordinates to parse
- * @return The new Bounds
- * @throws RequestHandlerBadRequestException If there was something wrong with
- * the coordinates
- */
- private static Bounds parseBounds(String coordinates) throws RequestHandlerBadRequestException {
- final Double[] coords = Stream.of(coordinates.split(",", -1)).map(Double::parseDouble).toArray(Double[]::new);
- // min lat, min lon, max lat, max lon
- final double minLat = Math.min(coords[1], coords[3]);
- final double maxLat = Math.max(coords[1], coords[3]);
- final double maxLon = Math.max(coords[0], coords[2]);
- final double minLon = Math.min(coords[0], coords[2]);
- final Bounds tBounds = new Bounds(minLat, minLon, maxLat, maxLon);
- if (tBounds.isOutOfTheWorld() || tBounds.isCollapsed()) {
- throw new RequestHandlerBadRequestException(
- tr("Bad bbox: {0} (converted to {1})", coordinates, tBounds.toString()));
- }
- return tBounds;
- }
-
- @Override
- protected void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException {
- if (crop != null && crop.toBBox().isInWorld()) {
- MainApplication.getLayerManager()
- .addLayer(new GpxLayer(DetectTaskingManagerUtils.createTaskingManagerGpxData(crop),
- DetectTaskingManagerUtils.MAPWITHAI_CROP_AREA));
- }
-
- final MapWithAILayer layer = MapWithAIDataUtils.getLayer(true);
-
- if (maxObj != null) {
- layer.setMaximumAddition(maxObj);
- }
- if (url != null) {
- // TODO make option for permanent url
- String tSource = source == null ? url : source;
- MapWithAIInfo info = new MapWithAIInfo(tSource, url);
- layer.setMapWithAIUrl(info);
- }
- if (switchLayer != null) {
- layer.setSwitchLayers(switchLayer);
- }
-
- if (download != null && download.toBBox().isInWorld()) {
- MapWithAIDataUtils.getMapWithAIData(layer, download);
- } else if (MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class).stream()
- .anyMatch(tLayer -> !(tLayer instanceof MapWithAILayer))) {
- MapWithAIDataUtils.getMapWithAIData(layer);
- } else if (crop != null && crop.toBBox().isInWorld()) {
- MapWithAIDataUtils.getMapWithAIData(layer, crop);
- }
- }
-
- @Override
- public String getPermissionMessage() {
- final String br = " ";
- final StringBuilder sb = new StringBuilder();
- sb.append(tr("Remote Control has been asked to load data from the API.")).append(" (").append(url).append(')')
- .append(br).append(tr("{0} will ", MapWithAIPlugin.NAME));
- if (Boolean.FALSE.equals(switchLayer)) {
- sb.append(tr("not "));
- }
- sb.append(tr("automatically switch layers.")).append(br);
- if (download != null) {
- sb.append(tr("We will download data in ")).append(download.toBBox().toStringCSV(",")).append(br);
- }
- if (crop != null) {
- sb.append(tr("We will crop the data to ")).append(crop.toBBox().toStringCSV(",")).append(br);
- }
- sb.append(tr("There is a maximum addition of {0} objects at one time", maxObj));
- return sb.toString();
-
- }
-
- @Override
- public PermissionPrefWithDefault getPermissionPref() {
- return PERMISSION_PREF_WITH_DEFAULT;
- }
-
- @Override
- public String[] getMandatoryParams() {
- return new String[] {};
- }
-
- @Override
- public String[] getOptionalParams() {
- return new String[] { BBOX, URL_STRING, MAX_OBJ, SWITCH_LAYER, CROP_BBOX };
- }
-
- @Override
- public String getUsage() {
- return tr("downloads {0} data", MapWithAIPlugin.NAME);
- }
-
- @Override
- public String[] getUsageExamples() {
- return new String[] { "/mapwithai", "/mapwithai?bbox=-108.4625421,39.0621223,-108.4594728,39.0633059",
- "/mapwithai?url=https://www.mapwith.ai/maps/ml_roads?conflate_with_osm=true"
- + "&theme=ml_road_vector&collaborator=josm"
- + "&token=ASb3N5o9HbX8QWn8G_NtHIRQaYv3nuG2r7_f3vnGld3KhZNCxg57IsaQyssIaEw5rfRNsPpMwg4TsnrSJtIJms5m"
- + "&hash=ASawRla3rBcwEjY4HIY&bbox={bbox}",
- "/mapwithai?bbox=-108.4625421,39.0621223,-108.4594728,39.0633059&max_obj=1",
- "/mapwithai?bbox=-108.4625421,39.0621223,-108.4594728,39.0633059&switch_layer=false",
- "/mapwithai?crop_bbox=-108.4625421,39.0621223,-108.4594728,39.0633059" };
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIUploadHook.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIUploadHook.java
deleted file mode 100644
index 43f1648d..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MapWithAIUploadHook.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.actions.UploadAction;
-import org.openstreetmap.josm.actions.upload.UploadHook;
-import org.openstreetmap.josm.plugins.PluginInformation;
-import org.openstreetmap.josm.tools.Destroyable;
-
-/**
- * Add information that is useful for QC/debugging to OSM changesets.
- *
- * @author Taylor Smock
- */
-public class MapWithAIUploadHook implements UploadHook, Destroyable {
- private final String version;
-
- /**
- * Create the upload hook
- *
- * @param info The info to get version information from
- */
- public MapWithAIUploadHook(PluginInformation info) {
- version = info.localversion;
- UploadAction.registerUploadHook(this);
- }
-
- @Override
- public void modifyChangesetTags(Map tags) {
- final Long addedObjects = MapWithAIDataUtils.getAddedObjects();
- if (addedObjects != 0) {
- tags.put("mapwithai", addedObjects.toString());
- final StringBuilder sb = new StringBuilder();
- sb.append("version=").append(version);
- if (MapWithAIPreferenceHelper.getMaximumAddition() != MapWithAIPreferenceHelper
- .getDefaultMaximumAddition()) {
- sb.append(";maxadd=").append(MapWithAIPreferenceHelper.getMaximumAddition());
- }
- if (DetectTaskingManagerUtils.hasTaskingManagerLayer()) {
- sb.append(";task=")
- .append(DetectTaskingManagerUtils.getTaskingManagerBounds().toBBox().toStringCSV(","));
- }
- if (!MapWithAIPreferenceHelper.getMapWithAIUrl().isEmpty()) {
- sb.append(";url_ids=")
- .append(String.join(";url_ids=",
- MapWithAIPreferenceHelper.getMapWithAIUrl().stream()
- .map(i -> i.getId() == null ? i.getUrl() : i.getId()).filter(Objects::nonNull)
- .collect(Collectors.joining(","))));
- }
- String mapwithaiOptions = sb.toString();
- if (mapwithaiOptions.length() > 255) {
- tags.put("mapwithai:options", mapwithaiOptions.substring(0, 255));
- int start = 255;
- int i = 1;
- while (start < mapwithaiOptions.length()) {
- tags.put("mapwithai:options:" + i, mapwithaiOptions.substring(start,
- start + 255 < mapwithaiOptions.length() ? start + 255 : mapwithaiOptions.length()));
- start = start + 255;
- i++;
- }
- } else {
- tags.put("mapwithai:options", mapwithaiOptions);
- }
- }
- }
-
- @Override
- public void destroy() {
- UploadAction.unregisterUploadHook(this);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MergeDuplicateWaysAction.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MergeDuplicateWaysAction.java
deleted file mode 100644
index a57b59ea..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/MergeDuplicateWaysAction.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.KeyEvent;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.openstreetmap.josm.actions.JosmAction;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.data.UndoRedoHandler;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.commands.MergeDuplicateWays;
-import org.openstreetmap.josm.tools.Shortcut;
-
-/**
- * An action that attempts to merge duplicate ways
- *
- * @author Taylor Smock
- */
-public class MergeDuplicateWaysAction extends JosmAction {
- private static final long serialVersionUID = 8971004636405132635L;
- private static final String DESCRIPTION = "Attempt to merge potential duplicate ways";
- /**
- * If there are 2 ways, we directly compare them to see if they are duplicates
- */
- private static final int COMPARE_WAYS_NUMBER = 2;
-
- /**
- * Create a new action
- */
- public MergeDuplicateWaysAction() {
- super(tr("{0}: ".concat(DESCRIPTION), MapWithAIPlugin.NAME), "mapwithai", tr(DESCRIPTION),
- Shortcut.registerShortcut("data:attemptmergeway", tr(DESCRIPTION), KeyEvent.VK_EXCLAMATION_MARK,
- Shortcut.ALT_CTRL_SHIFT),
- true, "mapwithai:attemptmergeway", true);
- setHelpId(ht("Plugin/MapWithAI"));
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- if (MainApplication.getLayerManager().getActiveDataSet() != null) {
- final List ways = new ArrayList<>(
- MainApplication.getLayerManager().getActiveDataSet().getSelectedWays());
- Command command = null;
- int i = 0;
- do {
- if (ways.size() == COMPARE_WAYS_NUMBER) {
- command = new MergeDuplicateWays(ways.get(0), ways.get(1));
- } else if (ways.size() == 1) {
- command = new MergeDuplicateWays(ways.get(0));
- } else if (ways.isEmpty()) {
- command = new MergeDuplicateWays(MainApplication.getLayerManager().getActiveDataSet());
- }
- if (command != null) {
- UndoRedoHandler.getInstance().add(command);
- i++;
- }
- } while ((command != null) && (i < 1));
- }
- }
-
- @Override
- public void updateEnabledState() {
- setEnabled(MainApplication.getLayerManager().getActiveDataSet() != null);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/TileXYZ.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/TileXYZ.java
deleted file mode 100644
index 1e6df34d..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/backend/TileXYZ.java
+++ /dev/null
@@ -1,121 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.backend;
-
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.data.Bounds;
-
-/**
- * Create a tile
- *
- * @param x The x coordinate of the tile
- * @param y The y coordinate of the tile
- * @param z The zoom level
- */
-record TileXYZ(int x, int y, int z) {
- /**
- * Checks to see if the given bounds are functionally equal to this tile
- *
- * @param left left
- * @param bottom bottom
- * @param right right
- * @param top top
- */
- boolean checkBounds(double left, double bottom, double right, double top) {
- final var thisLeft = xToLongitude(this.x, this.z);
- final var thisRight = xToLongitude(this.x + 1, this.z);
- final var thisBottom = yToLatitude(this.y + 1, this.z);
- final var thisTop = yToLatitude(this.y, this.z);
- return equalsEpsilon(thisLeft, left, this.z) && equalsEpsilon(thisRight, right, this.z)
- && equalsEpsilon(thisBottom, bottom, this.z) && equalsEpsilon(thisTop, top, this.z);
- }
-
- private static boolean equalsEpsilon(double first, double second, int z) {
- // 0.1% of tile size is considered to be "equal"
- final var maxDiff = (360 / Math.pow(2, z)) / 1000;
- final var diff = Math.abs(first - second);
- return diff <= maxDiff;
- }
-
- private static double xToLongitude(int x, int z) {
- return (x / Math.pow(2, z)) * 360 - 180;
- }
-
- private static double yToLatitude(int y, int z) {
- var t = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
- return 180 / Math.PI * Math.atan((Math.exp(t) - Math.exp(-t)) / 2);
- }
-
- /**
- * Convert bounds to tiles
- *
- * @param zoom The zoom level to use
- * @param bounds The bounds to convert to tiles
- * @return A stream of tiles for the bounds at the given zoom level
- */
- static Stream tilesFromBBox(int zoom, Bounds bounds) {
- final var left = bounds.getMinLon();
- final var bottom = bounds.getMinLat();
- final var right = bounds.getMaxLon();
- final var top = bounds.getMaxLat();
- final var tile1 = tileFromLatLonZoom(left, bottom, zoom);
- final var tile2 = tileFromLatLonZoom(right, top, zoom);
- return IntStream.rangeClosed(tile1.x, tile2.x)
- .mapToObj(x -> IntStream.rangeClosed(tile2.y, tile1.y).mapToObj(y -> new TileXYZ(x, y, zoom)))
- .flatMap(stream -> stream);
- }
-
- /**
- * Checks to see if the given bounds are functionally equal to this tile
- *
- * @param left left lon
- * @param bottom bottom lat
- * @param right right lon
- * @param top top lat
- */
- static TileXYZ tileFromBBox(double left, double bottom, double right, double top) {
- var zoom = 18;
- while (zoom > 0) {
- final var tile1 = tileFromLatLonZoom(left, bottom, zoom);
- final var tile2 = tileFromLatLonZoom(right, top, zoom);
- if (tile1.equals(tile2)) {
- return tile1;
- } else if (tile1.checkBounds(left, bottom, right, top)) {
- return tile1;
- } else if (tile2.checkBounds(left, bottom, right, top)) {
- return tile2;
- // Just in case the coordinates are _barely_ in other tiles and not the "common"
- // tile
- } else if (Math.abs(tile1.x() - tile2.x()) <= 2 && Math.abs(tile1.y() - tile2.y()) <= 2) {
- final var tileT = new TileXYZ((tile1.x() + tile2.x()) / 2, (tile1.y() + tile2.y()) / 2, zoom);
- if (tileT.checkBounds(left, bottom, right, top)) {
- return tileT;
- }
- }
- zoom--;
- }
- return new TileXYZ(0, 0, 0);
- }
-
- static TileXYZ tileFromLatLonZoom(double lon, double lat, int zoom) {
- var xCoordinate = Math.toIntExact(Math.round(Math.floor(Math.pow(2, zoom) * (180 + lon) / 360)));
- var yCoordinate = Math.toIntExact(Math.round(Math.floor(Math.pow(2, zoom)
- * (1 - (Math.log(Math.tan(Math.toRadians(lat)) + 1 / Math.cos(Math.toRadians(lat))) / Math.PI)) / 2)));
- return new TileXYZ(xCoordinate, yCoordinate, zoom);
- }
-
- /**
- * Extends a bounds object to contain this tile
- *
- * @param currentBounds The bounds to extend
- */
- void expandBounds(Bounds currentBounds) {
- final var thisLeft = xToLongitude(this.x, this.z);
- final var thisRight = xToLongitude(this.x + 1, this.z);
- final var thisBottom = yToLatitude(this.y + 1, this.z);
- final var thisTop = yToLatitude(this.y, this.z);
- currentBounds.extend(thisBottom, thisLeft);
- currentBounds.extend(thisTop, thisRight);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AbstractConflationCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AbstractConflationCommand.java
deleted file mode 100644
index df11e73b..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AbstractConflationCommand.java
+++ /dev/null
@@ -1,194 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.awt.GraphicsEnvironment;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
-import org.openstreetmap.josm.data.osm.PrimitiveId;
-import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.io.DownloadPrimitivesTask;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.openstreetmap.josm.gui.progress.ProgressMonitor;
-import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
-import org.openstreetmap.josm.tools.Pair;
-
-/**
- * This is an abstract class for conflation commands. This class is primarily
- * used in {@link CreateConnectionsCommand#createConnections}.
- *
- * @author Taylor Smock
- *
- */
-public abstract class AbstractConflationCommand extends Command {
- protected Collection possiblyAffectedPrimitives;
-
- /**
- * Creates a new command in the context of a specific data set, without data
- * layer
- *
- * @param data the data set. Must not be null.
- */
- protected AbstractConflationCommand(DataSet data) {
- super(data);
- }
-
- @Override
- public void fillModifiedData(Collection modified, Collection deleted,
- Collection added) {
- // Do nothing -- the sequence commands should take care of it.
- }
-
- /**
- * Only return Node/Way/Relation here. It can be any combination.
- *
- * @return The types of primitive that the command is interested in
- */
- public abstract Collection> getInterestedTypes();
-
- /**
- * Return the key that the command uses to perform conflation. For example,
- * `conn` or `dupe`.
- *
- * @return The key that the command is interested in
- */
- public abstract String getKey();
-
- /**
- * Get the actual command to run. This should not be normally overriden by
- * subclasses. Override {@link AbstractConflationCommand#getRealCommand}
- * instead.
- *
- * @param primitives The primitives to run the command on
- * @return The command that will be run (may be {@code null})
- */
- public Command getCommand(Collection primitives) {
- possiblyAffectedPrimitives = new HashSet<>(primitives);
- return getRealCommand();
- }
-
- /**
- * A command that performs the conflation steps.
- *
- * @return The command to do whatever is required for the result
- */
- public abstract Command getRealCommand();
-
- /**
- * Get the primitives from a dataset with specified ids
- *
- * @param dataSet The dataset holding the primitives (hopefully)
- * @param ids The ids formated like
- * n<NUMBER>,r<NUMBER>,w<NUMBER>
- * @return The primitives that the ids point to, if in the dataset.
- */
- public static OsmPrimitive[] getPrimitives(DataSet dataSet, String ids) {
- Objects.requireNonNull(dataSet, tr("DataSet cannot be null"));
- Objects.requireNonNull(ids, tr("The ids string cannot be null"));
- final Map> missingPrimitives = new TreeMap<>();
- final String[] connections = ids.split(",", -1);
- final OsmPrimitive[] primitiveConnections = new OsmPrimitive[connections.length];
- for (int i = 0; i < connections.length; i++) {
- final String member = connections[i];
- try {
- SimplePrimitiveId primitiveId = SimplePrimitiveId.fromString(member);
- primitiveConnections[i] = dataSet.getPrimitiveById(primitiveId);
- if (primitiveConnections[i] == null) {
- missingPrimitives.put(i, new Pair<>(primitiveId.getUniqueId(), primitiveId.getType()));
- }
- } catch (IllegalArgumentException e) {
- // Assume someone fiddled with the tag if the pattern doesn't match.
- if (!e.getMessage().contains("n|node|w|way|r|rel|relation")) {
- throw e;
- }
- }
- }
- obtainMissingPrimitives(dataSet, primitiveConnections, missingPrimitives);
- return primitiveConnections;
- }
-
- private static void obtainMissingPrimitives(DataSet dataSet, OsmPrimitive[] primitiveConnections,
- Map> missingPrimitives) {
- if (!missingPrimitives.isEmpty()) {
- final Map ids = missingPrimitives.entrySet().stream().collect(Collectors
- .toMap(entry -> new SimplePrimitiveId(entry.getValue().a, entry.getValue().b), Map.Entry::getKey));
- final List toFetch = new ArrayList<>(ids.keySet());
- final Optional optionalLayer = MainApplication.getLayerManager()
- .getLayersOfType(OsmDataLayer.class).stream().filter(layer -> layer.getDataSet().equals(dataSet))
- .findFirst();
-
- final String generatedLayerName = "EvKlVarShAiAllsM generated layer";
- final OsmDataLayer layer = optionalLayer
- .orElseGet(() -> new OsmDataLayer(dataSet, generatedLayerName, null));
-
- final ProgressMonitor monitor;
- if (GraphicsEnvironment.isHeadless()) {
- monitor = NullProgressMonitor.INSTANCE;
- } else {
- monitor = new PleaseWaitProgressMonitor(tr("Downloading additional OsmPrimitives"));
- }
- final DownloadPrimitivesTask downloadPrimitivesTask = new DownloadPrimitivesTask(layer, toFetch, true,
- monitor);
- downloadPrimitivesTask.run();
- for (final Map.Entry entry : ids.entrySet()) {
- final int index = entry.getValue();
- final OsmPrimitive primitive = dataSet.getPrimitiveById(entry.getKey());
- primitiveConnections[index] = primitive;
- }
-
- if (generatedLayerName.equals(layer.getName())) {
- layer.destroy();
- }
- }
- }
-
- @Override
- public Collection extends OsmPrimitive> getParticipatingPrimitives() {
- // This is used for debugging. Default to anything that might be affected.
- return Collections.unmodifiableCollection(this.possiblyAffectedPrimitives);
- }
-
- /**
- * Use this to ensure that something that cannot be undone without errors isn't
- * undone.
- *
- * @return true if the command should show as a separate command in the
- * undo/redo lists
- */
- public abstract boolean allowUndo();
-
- /**
- * `conn` and `dupe` should not exist in OSM, but `addr:street` should. This is
- * used in a validation test.
- *
- * @return {@code true} if the key should not exist in OpenStreetMap
- */
- public abstract boolean keyShouldNotExistInOSM();
-
- /**
- * If another command conflicts with this command, it should be returned here.
- * For example, if one command adds an addr node to a building, and another
- * command does the reverse, they conflict.
- *
- * @return Conflation commands that conflict with this conflation command
- */
- public Collection> conflictedCommands() {
- return Collections.emptyList();
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AddNodeToWayCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AddNodeToWayCommand.java
deleted file mode 100644
index 04956b8f..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AddNodeToWayCommand.java
+++ /dev/null
@@ -1,141 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.command.ChangeCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.data.osm.IWaySegment;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
-import org.openstreetmap.josm.tools.Geometry;
-import org.openstreetmap.josm.tools.Logging;
-
-/**
- * Add a node to a way
- */
-public class AddNodeToWayCommand extends Command {
- private final Node toAddNode;
- private final Way way;
- private final Node firstNode;
- private final Node secondNode;
- private Command changeCommand;
-
- /**
- * Add a node to a way in an undoable manner
- *
- * @param toAddNode The node to add
- * @param way The way to add the node to
- * @param first The node that comes before the node to add
- * @param second The node that comes after the node to add
- */
- public AddNodeToWayCommand(Node toAddNode, Way way, Node first, Node second) {
- super(way.getDataSet());
- this.toAddNode = toAddNode;
- this.way = way;
- this.firstNode = first;
- this.secondNode = second;
- }
-
- @Override
- public boolean executeCommand() {
- int index = Integer.MIN_VALUE;
- try {
- // IWaySegment#forNodePair throws an IllegalArgumentException when the node pair
- // doesn't exist as a segment in the way.
- IWaySegment.forNodePair(getWay(), getFirstNode(), getSecondNode());
- index = Math.max(getWay().getNodes().indexOf(getFirstNode()), getWay().getNodes().indexOf(getSecondNode()));
- } catch (IllegalArgumentException e) {
- Logging.trace(e);
- // OK, someone has added a node between the two nodes since calculation
- Way tWay = new Way();
- tWay.setNodes(Arrays.asList(getFirstNode(), getSecondNode()));
- List relevantNodes = getWay().getNodes().stream()
- .filter(node -> Geometry.getDistance(tWay, node) < MapWithAIPreferenceHelper.getMaxNodeDistance())
- .collect(Collectors.toList());
- for (int i = 0; i < relevantNodes.size() - 1; i++) {
- Way tWay2 = new Way();
- tWay2.setNodes(Arrays.asList(relevantNodes.get(i), relevantNodes.get(i + 1)));
- if (Geometry.getDistance(tWay2, getToAddNode()) < MapWithAIPreferenceHelper.getMaxNodeDistance()) {
- index = Math.max(way.getNodes().indexOf(tWay2.firstNode()),
- way.getNodes().indexOf(tWay2.lastNode()));
- }
- }
- }
- if (index != Integer.MIN_VALUE && changeCommand == null) {
- Way tWay = new Way(getWay());
- tWay.addNode(index, getToAddNode());
- changeCommand = new ChangeCommand(getWay(), tWay);
- }
- if (changeCommand != null) {
- changeCommand.executeCommand();
- }
- return true;
- }
-
- @Override
- public void undoCommand() {
- if (changeCommand != null) {
- changeCommand.undoCommand();
- }
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Add node to way");
- }
-
- @Override
- public void fillModifiedData(Collection modified, Collection deleted,
- Collection added) {
- modified.addAll(Arrays.asList(getToAddNode(), getWay()));
- }
-
- @Override
- public Collection extends OsmPrimitive> getParticipatingPrimitives() {
- return changeCommand.getParticipatingPrimitives();
- }
-
- /**
- * Get the node that will be added to a way
- *
- * @return {@link Node} to add to {@link Way}
- */
- public Node getToAddNode() {
- return toAddNode;
- }
-
- /**
- * Get the way that we are modifying
- *
- * @return {@link Way} that we are adding a {@link Node} to
- */
- public Way getWay() {
- return way;
- }
-
- /**
- * Get the node that we are adding our node after
- *
- * @return {@link Node} that we are adding another {@link Node} after.
- */
- public Node getFirstNode() {
- return firstNode;
- }
-
- /**
- * Get the node that we are adding our node before
- *
- * @return {@link Node} that we are adding another {@link Node} before.
- */
- public Node getSecondNode() {
- return secondNode;
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AlreadyConflatedCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AlreadyConflatedCommand.java
deleted file mode 100644
index 77b84035..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/AlreadyConflatedCommand.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.plugins.mapwithai.data.mapwithai.PreConflatedDataUtils;
-
-/**
- * All this currently does is remove
- * {@link PreConflatedDataUtils#CONFLATED_KEY}.
- *
- * @author Taylor Smock
- *
- */
-public class AlreadyConflatedCommand extends AbstractConflationCommand {
-
- public AlreadyConflatedCommand(DataSet data) {
- super(data);
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Remove key for already conflated data");
- }
-
- @Override
- public Collection> getInterestedTypes() {
- return Arrays.asList(Node.class, Way.class, Relation.class);
- }
-
- @Override
- public String getKey() {
- return PreConflatedDataUtils.getConflatedKey();
- }
-
- @Override
- public Command getRealCommand() {
- List commands = possiblyAffectedPrimitives.stream().filter(p -> p.hasTag(getKey()))
- .map(k -> new ChangePropertyCommand(k, getKey(), "")).collect(Collectors.toList());
- return commands.isEmpty() ? null : SequenceCommand.wrapIfNeeded(getDescriptionText(), commands);
- }
-
- @Override
- public boolean allowUndo() {
- return true;
- }
-
- @Override
- public boolean keyShouldNotExistInOSM() {
- return true;
- }
-
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/ConnectedCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/ConnectedCommand.java
deleted file mode 100644
index 13bba444..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/ConnectedCommand.java
+++ /dev/null
@@ -1,122 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.command.ChangeCommand;
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.projection.ProjectionRegistry;
-import org.openstreetmap.josm.tools.Geometry;
-import org.openstreetmap.josm.tools.Logging;
-
-/**
- * Connect a way to another way (in between nodes)
- */
-public class ConnectedCommand extends AbstractConflationCommand {
- public static final String KEY = "conn";
-
- public ConnectedCommand(DataSet data) {
- super(data);
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Connect nodes to ways");
- }
-
- /**
- * Add a node to a way
- *
- * @param toAddNode The node to add
- * @param way The way to add the node to
- * @param first The first node in a waysegment (the node is between this and
- * the second node)
- * @param second The second node in a waysegment
- * @return Commands to add a node to a way, or null if it won't be done
- */
- public static List addNodesToWay(Node toAddNode, Way way, Node first, Node second) {
- List tCommands = new ArrayList<>();
- final Way tWay = new Way();
- final List ways = toAddNode.getReferrers().stream().filter(w -> !w.equals(way))
- .filter(Way.class::isInstance).map(Way.class::cast).collect(Collectors.toList());
- tWay.addNode(first);
- tWay.addNode(second);
- final double distance = Geometry.getDistanceWayNode(tWay, toAddNode);
- if (distance < 5) {
- if (!ways.isEmpty()) {
- int index = ways.get(0).getNodes().indexOf(toAddNode);
- final Node node4 = index == 0 ? ways.get(0).getNode(1) : ways.get(0).getNode(index - 1);
- final Node tNode = new Node(toAddNode);
- tNode.setCoor(ProjectionRegistry.getProjection().eastNorth2latlon(Geometry.getLineLineIntersection(
- first.getEastNorth(), second.getEastNorth(), toAddNode.getEastNorth(), node4.getEastNorth())));
- tCommands.add(new ChangeCommand(toAddNode, tNode));
- }
- tCommands.add(new AddNodeToWayCommand(toAddNode, way, first, second));
- }
- return tCommands;
- }
-
- private static List connectedCommand(DataSet dataSet, Node node) {
- final List commands = new ArrayList<>();
- final OsmPrimitive[] primitiveConnections = getPrimitives(dataSet, node.get(KEY));
- for (int i = 0; i < primitiveConnections.length / 3; i++) {
- if (primitiveConnections[i] instanceof Way && primitiveConnections[i + 1] instanceof Node
- && primitiveConnections[i + 2] instanceof Node) {
- final List addNodesToWayCommand = addNodesToWay(node, (Way) primitiveConnections[i],
- (Node) primitiveConnections[i + 1], (Node) primitiveConnections[i + 2]);
- commands.addAll(addNodesToWayCommand);
- } else {
- Logging.error("MapWithAI: Cannot create connections ({0}: {1}, {2}: {3}, {4}: {5})", i,
- primitiveConnections[i] == null ? null : primitiveConnections[i].getClass(), i + 1,
- primitiveConnections[i + 1] == null ? null : primitiveConnections[i + 1].getClass(), i + 2,
- primitiveConnections[i + 2] == null ? null : primitiveConnections[i + 2].getClass());
- }
- }
- commands.add(new ChangePropertyCommand(node, KEY, null));
- return commands;
- }
-
- @Override
- public Collection> getInterestedTypes() {
- return Collections.singletonList(Node.class);
- }
-
- @Override
- public String getKey() {
- return KEY;
- }
-
- @Override
- public Command getRealCommand() {
- final List commands = new ArrayList<>();
- possiblyAffectedPrimitives.stream().filter(Node.class::isInstance).map(Node.class::cast)
- .forEach(node -> commands.addAll(connectedCommand(getAffectedDataSet(), node)));
- Command returnCommand = null;
- if (!commands.isEmpty()) {
- returnCommand = new SequenceCommand(getDescriptionText(), commands);
- }
- return returnCommand;
- }
-
- @Override
- public boolean allowUndo() {
- return false;
- }
-
- @Override
- public boolean keyShouldNotExistInOSM() {
- return true;
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/CreateConnectionsCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/CreateConnectionsCommand.java
deleted file mode 100644
index 34f322ee..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/CreateConnectionsCommand.java
+++ /dev/null
@@ -1,186 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.UndoRedoHandler;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveData;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.commands.cleanup.MissingConnectionTags;
-import org.openstreetmap.josm.plugins.mapwithai.commands.cleanup.OverNodedWays;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * Create connections
- */
-public class CreateConnectionsCommand extends Command {
- private final Collection primitives;
- private Command command;
- private Command undoCommands;
- private static final LinkedHashSet> CONFLATION_COMMANDS = new LinkedHashSet<>();
- static {
- CONFLATION_COMMANDS.add(MissingConnectionTags.class);
- CONFLATION_COMMANDS.add(ConnectedCommand.class);
- CONFLATION_COMMANDS.add(DuplicateCommand.class);
- CONFLATION_COMMANDS.add(MergeAddressBuildings.class);
- CONFLATION_COMMANDS.add(MergeBuildingAddress.class);
- CONFLATION_COMMANDS.add(OverNodedWays.class);
- CONFLATION_COMMANDS.add(AlreadyConflatedCommand.class);
- }
-
- /**
- * Create a new command
- *
- * @param data The dataset
- * @param primitives The primitives to connect
- */
- public CreateConnectionsCommand(DataSet data, Collection primitives) {
- super(data);
- this.primitives = primitives;
- }
-
- @Override
- public boolean executeCommand() {
- if (command == null) {
- List commands = createConnections(getAffectedDataSet(), primitives);
- command = commands.get(0);
- undoCommands = commands.get(1);
- }
- if (command != null) {
- command.executeCommand();
- }
- if (undoCommands != null && !UndoRedoHandler.getInstance().getUndoCommands().contains(undoCommands)) {
- GuiHelper.runInEDTAndWait(() -> UndoRedoHandler.getInstance().add(undoCommands));
- }
- return true;
- }
-
- @Override
- public void undoCommand() {
- if (command != null) {
- command.undoCommand();
- }
- }
-
- /**
- * Create connections based off of current MapWithAI syntax
- *
- * @param dataSet The {@link DataSet} that should have the primitives we are
- * connecting to
- * @param collection The primitives with connection information (currently only
- * checks Nodes)
- * @return A list {@link Command} to create connections with (first is one that
- * can be folded into other commands, second is one that should be
- * undoable individually)
- */
- public static List createConnections(DataSet dataSet, Collection collection) {
- final List permanent = new ArrayList<>();
- final List undoable = new ArrayList<>();
- List> runCommands = new ArrayList<>();
- for (final Class extends AbstractConflationCommand> abstractCommandClass : getConflationCommands()) {
- final AbstractConflationCommand abstractCommand;
- try {
- abstractCommand = abstractCommandClass.getConstructor(DataSet.class).newInstance(dataSet);
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException | NoSuchMethodException | SecurityException e) {
- Logging.debug(e);
- continue;
- }
- // If there are conflicting commands, don't add it.
- if (runCommands.stream().anyMatch(c -> abstractCommand.conflictedCommands().contains(c))) {
- continue;
- }
- final Collection realPrimitives = collection.stream().map(dataSet::getPrimitiveById)
- .filter(Objects::nonNull).collect(Collectors.toList());
- final Collection tPrimitives = new TreeSet<>();
- abstractCommand.getInterestedTypes()
- .forEach(clazz -> tPrimitives.addAll(Utils.filteredCollection(realPrimitives, clazz)));
-
- final Command actualCommand = abstractCommand.getCommand(
- tPrimitives.stream().filter(prim -> prim.hasKey(abstractCommand.getKey()) && !prim.isDeleted())
- .collect(Collectors.toList()));
- if (Objects.nonNull(actualCommand)) {
- if (abstractCommand.allowUndo()) {
- undoable.add(actualCommand);
- } else {
- permanent.add(actualCommand);
- }
- runCommands.add(abstractCommand.getClass());
- }
- }
-
- Command permanentCommand = permanent.isEmpty() ? null
- : SequenceCommand.wrapIfNeeded(getRealDescriptionText(), permanent);
- Command undoCommand = undoable.isEmpty() ? null
- : SequenceCommand.wrapIfNeeded(getRealDescriptionText(), undoable);
-
- return Arrays.asList(permanentCommand, undoCommand);
- }
-
- @Override
- public String getDescriptionText() {
- return getRealDescriptionText();
- }
-
- private static String getRealDescriptionText() {
- return tr("Create connections from {0} data", MapWithAIPlugin.NAME);
- }
-
- @Override
- public void fillModifiedData(Collection modified, Collection deleted,
- Collection added) {
- command.fillModifiedData(modified, deleted, added);
- }
-
- @Override
- public Collection extends OsmPrimitive> getParticipatingPrimitives() {
- return command.getParticipatingPrimitives();
- }
-
- /**
- * Add third-party commands that are run when conflating data.
- *
- * @param command A command to run when copying data from the MapWithAI layer
- */
- public static void addConflationCommand(Class extends AbstractConflationCommand> command) {
- CONFLATION_COMMANDS.add(command);
- }
-
- /**
- * Get the commands to run when conflating data.
- *
- * @return A set of commands to run when copying data from the MapWithAI layer
- */
- public static Set> getConflationCommands() {
- return Collections.unmodifiableSet(CONFLATION_COMMANDS);
- }
-
- /**
- * Remove a command that runs when conflating data.
- *
- * @param command The command class to remove
- * @return {@code true} if the conflation command was removed and was present
- * @see List#remove
- */
- public static boolean removeConflationCommand(Class extends AbstractConflationCommand> command) {
- return CONFLATION_COMMANDS.remove(command);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/DuplicateCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/DuplicateCommand.java
deleted file mode 100644
index 5e572cc8..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/DuplicateCommand.java
+++ /dev/null
@@ -1,125 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.actions.MergeNodesAction;
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * Connect duplicate nodes
- */
-public class DuplicateCommand extends AbstractConflationCommand {
- public static final String KEY = "dupe";
-
- public DuplicateCommand(DataSet data) {
- super(data);
- }
-
- private static List duplicateNode(DataSet dataSet, Node node) {
- final OsmPrimitive[] primitiveConnections = getPrimitives(dataSet, node.get(KEY));
- if (primitiveConnections.length != 1) {
- Logging.error("{0}: {3} connection connected to more than one node? ({3}={1})", MapWithAIPlugin.NAME,
- node.get(KEY), KEY);
- }
-
- final List commands = new ArrayList<>();
- if (primitiveConnections[0] instanceof Node && !primitiveConnections[0].isDeleted()) {
- final Node replaceNode = (Node) primitiveConnections[0];
- final Command tCommand = replaceNode(node, replaceNode);
- if (tCommand != null) {
- commands.add(tCommand);
- if (replaceNode.hasKey(KEY)) {
- final String key = replaceNode.get(KEY);
- commands.add(new ChangePropertyCommand(replaceNode, KEY, key));
- } else {
- replaceNode.put(KEY, "empty_value"); // This is needed to actually have a command.
- commands.add(new ChangePropertyCommand(replaceNode, KEY, null));
- replaceNode.remove(KEY);
- }
- }
- }
- if (commands.isEmpty()) {
- commands.add(new ChangePropertyCommand(node, KEY, null));
- }
- return commands;
- }
-
- /**
- * Replace nodes that are in the same location
- *
- * @param original The original node (the one to replace)
- * @param newNode The node that is replacing the original node
- * @return A command that replaces the node, or null if they are not at the same
- * location.
- */
- public static Command replaceNode(Node original, Node newNode) {
- Command tCommand = null;
- if (original.equalsEpsilon(newNode)) {
- tCommand = MergeNodesAction.mergeNodes(Collections.singletonList(original), newNode, newNode);
- }
- return tCommand;
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Remove duplicated nodes");
- }
-
- @Override
- public Collection> getInterestedTypes() {
- return Collections.singletonList(Node.class);
- }
-
- @Override
- public String getKey() {
- return KEY;
- }
-
- @Override
- public Command getRealCommand() {
- final List commands = new ArrayList<>();
- for (Node tNode : Utils.filteredCollection(possiblyAffectedPrimitives, Node.class).stream().distinct()
- .filter(node -> node.hasKey(KEY)).collect(Collectors.toList())) {
- List tCommands = duplicateNode(getAffectedDataSet(), tNode);
- // We have to execute the command to avoid duplicating the command later. Undo
- // occurs later, so that the state doesn't actually change.
- tCommands.forEach(Command::executeCommand);
- commands.addAll(tCommands);
- }
- Collections.reverse(commands);
- commands.forEach(Command::undoCommand);
- Collections.reverse(commands);
- Command returnCommand = null;
- if (commands.size() == 1) {
- returnCommand = commands.get(0);
- } else if (!commands.isEmpty()) {
- returnCommand = new SequenceCommand(getDescriptionText(), commands);
- }
- return returnCommand;
- }
-
- @Override
- public boolean allowUndo() {
- return false;
- }
-
- @Override
- public boolean keyShouldNotExistInOSM() {
- return true;
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MapWithAIAddCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MapWithAIAddCommand.java
deleted file mode 100644
index 01b0d943..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MapWithAIAddCommand.java
+++ /dev/null
@@ -1,215 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.locks.Lock;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.UndoRedoHandler;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveData;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.backend.GetDataRunnable;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAILayer;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Pair;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * Add data from the MapWithAI layer to the OSM data layer
- */
-public class MapWithAIAddCommand extends Command implements Runnable {
- private final DataSet editable;
- private final DataSet mapWithAI;
- private final Collection primitives;
- Command command;
- private Lock lock;
- final Map sources;
-
- /**
- * Add primitives from MapWithAI to the OSM data layer
- *
- * @param mapWithAILayer The MapWithAI layer
- * @param editLayer The OSM layer
- * @param selection The primitives to add from MapWithAI
- */
- public MapWithAIAddCommand(MapWithAILayer mapWithAILayer, OsmDataLayer editLayer,
- Collection selection) {
- this(mapWithAILayer.getDataSet(), editLayer.getDataSet(), selection);
- lock = mapWithAILayer.getLock();
- }
-
- /**
- * Add primitives from MapWithAI to the OSM data layer
- *
- * @param mapWithAI The MapWithAI dataset
- * @param editable The OSM dataset
- * @param selection The primitives to add from MapWithAI
- */
- public MapWithAIAddCommand(DataSet mapWithAI, DataSet editable, Collection selection) {
- super(editable);
- this.mapWithAI = mapWithAI;
- this.editable = editable;
- Collection nodeReferrers = Utils.filteredCollection(selection, Node.class).stream().map(Node::getReferrers)
- .flatMap(List::stream).filter(Way.class::isInstance).map(Way.class::cast).collect(Collectors.toList());
- this.primitives = new HashSet<>(selection);
- this.primitives.addAll(nodeReferrers);
- sources = selection.stream()
- .map(prim -> new Pair<>(prim,
- prim.hasKey("source") ? prim.get("source")
- : prim.get(GetDataRunnable.MAPWITHAI_SOURCE_TAG_KEY)))
- .filter(pair -> pair.b != null).collect(Collectors.toMap(pair -> pair.a, pair -> pair.b));
- }
-
- @Override
- public boolean executeCommand() {
- GuiHelper.runInEDTAndWait(this);
- return true;
- }
-
- @Override
- public void run() {
- if (mapWithAI.equals(editable)) {
- Logging.error("{0}: DataSet mapWithAI ({1}) should not be the same as DataSet editable ({2})",
- MapWithAIPlugin.NAME, mapWithAI, editable);
- throw new IllegalArgumentException();
- }
- synchronized (this) {
- try {
- if (lock != null) {
- lock.lock();
- }
- if (command == null) {// needed for undo/redo (don't create a new command)
- final List allPrimitives = new ArrayList<>();
- MapWithAIDataUtils.addPrimitivesToCollection(allPrimitives, primitives);
- Collection primitiveData = new HashSet<>();
- final Command movePrimitivesCommand = new MovePrimitiveDataSetCommand(editable, mapWithAI,
- primitives, primitiveData);
- final Command createConnectionsCommand = createConnections(editable, primitiveData);
- command = SequenceCommand.wrapIfNeeded(getDescriptionText(), movePrimitivesCommand,
- createConnectionsCommand);
- }
- GuiHelper.runInEDTAndWait(command::executeCommand);
- } finally {
- if (lock != null) {
- lock.unlock();
- }
- }
- }
- }
-
- /**
- * Create connections based off of current MapWithAI syntax
- *
- * @param dataSet The {@link DataSet} that should have the primitives we are
- * connecting to
- * @param collection The primitives with connection information (currently only
- * checks Nodes)
- * @return A Command to create connections from the collection
- */
- public static Command createConnections(DataSet dataSet, Collection collection) {
- return new CreateConnectionsCommand(dataSet, collection);
- }
-
- @Override
- public void undoCommand() {
- try {
- if (lock != null) {
- lock.lock();
- }
- synchronized (this) {
- if (command != null) {
- GuiHelper.runInEDTAndWait(command::undoCommand);
- }
- }
- } finally {
- if (lock != null) {
- lock.unlock();
- }
- }
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Add object from {0}", MapWithAIPlugin.NAME);
- }
-
- @Override
- public void fillModifiedData(Collection modified, Collection deleted,
- Collection added) {
- modified.addAll(primitives);
- }
-
- @Override
- public Collection extends OsmPrimitive> getParticipatingPrimitives() {
- final Command tCommand;
- synchronized (this) {
- tCommand = this.command;
- }
- return Stream
- .of(Optional.ofNullable(tCommand).map(Command::getParticipatingPrimitives)
- .orElseGet(Collections::emptySet), primitives)
- .flatMap(Collection::stream).collect(Collectors.toSet());
- }
-
- /**
- * Calculate the number of objects added in this command that are not deleted
- * (may not count significantly modified objects as well).
- *
- * @return The number of MapWithAI objects added in this command that are not
- * deleted
- */
- public long getAddedObjects() {
- long returnLong;
- if (this.equals(UndoRedoHandler.getInstance().getLastCommand())) {
- returnLong = primitives.size();
- } else {
- returnLong = primitives.stream().map(editable::getPrimitiveById).filter(Objects::nonNull)
- .filter(MapWithAIAddCommand::validPrimitive).count();
- }
- return returnLong;
- }
-
- public Collection getSourceTags() {
- return sources.entrySet().stream().filter(entry -> validPrimitive(editable.getPrimitiveById(entry.getKey())))
- .map(Map.Entry::getValue).filter(Objects::nonNull).distinct().sorted().collect(Collectors.toList());
- }
-
- private static boolean validPrimitive(OsmPrimitive prim) {
- return prim != null && (!prim.isDeleted() || prim instanceof Node);
- }
-
- @Override
- public boolean equals(Object other) {
- if (other instanceof MapWithAIAddCommand) {
- MapWithAIAddCommand o = (MapWithAIAddCommand) other;
- return Objects.equals(this.editable, o.editable) && Objects.equals(this.mapWithAI, o.mapWithAI)
- && Objects.equals(this.lock, o.lock) && o.primitives.containsAll(this.primitives)
- && this.primitives.containsAll(o.primitives);
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(editable, mapWithAI, primitives, lock);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeAddressBuildings.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeAddressBuildings.java
deleted file mode 100644
index 10a68336..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeAddressBuildings.java
+++ /dev/null
@@ -1,144 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.IPrimitive;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
-import org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry.ReplaceGeometryUtils;
-import org.openstreetmap.josm.tools.Geometry;
-
-/**
- * Merge buildings with pre-existing addresses
- *
- * @author Taylor Smock
- *
- */
-public class MergeAddressBuildings extends AbstractConflationCommand {
- public static final String KEY = "building";
- public static final String SOURCE = "source";
-
- public MergeAddressBuildings(DataSet data) {
- super(data);
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Merge added buildings with existing address nodes");
- }
-
- @Override
- public Collection> getInterestedTypes() {
- return Arrays.asList(Way.class, Relation.class);
- }
-
- @Override
- public String getKey() {
- return KEY;
- }
-
- @Override
- public Command getRealCommand() {
- List commands = new ArrayList<>();
- if (MapWithAIPreferenceHelper.isMergeBuildingAddress()) {
- final List mergedNodes = new ArrayList<>();
- possiblyAffectedPrimitives.stream().filter(Way.class::isInstance).map(Way.class::cast)
- .filter(way -> way.hasKey(KEY)).filter(Way::isClosed)
- .forEach(way -> commands.addAll(mergeAddressBuilding(getAffectedDataSet(), way, mergedNodes)));
-
- possiblyAffectedPrimitives.stream().filter(Relation.class::isInstance).map(Relation.class::cast)
- .filter(rel -> rel.hasKey(KEY)).filter(Relation::isMultipolygon)
- .forEach(rel -> commands.addAll(mergeAddressBuilding(getAffectedDataSet(), rel, mergedNodes)));
- }
-
- Command returnCommand = null;
- if (commands.size() == 1) {
- returnCommand = commands.get(0);
- } else if (!commands.isEmpty()) {
- returnCommand = new SequenceCommand(getDescriptionText(), commands);
- }
- return returnCommand;
- }
-
- /**
- * Merge a building with an address node
- *
- * @param affectedDataSet The dataset to use
- * @param object The object to merge with an address node
- * @param mergedNodes The nodes already merged. This will be modified in
- * this method!
- * @return The command to merge an address onto a building
- */
- private static Collection extends Command> mergeAddressBuilding(DataSet affectedDataSet, OsmPrimitive object,
- Collection mergedNodes) {
- final List toCheck = new ArrayList<>(affectedDataSet.searchNodes(object.getBBox()));
- toCheck.removeIf(IPrimitive::isDeleted);
- final Collection nodesInside = Geometry.filterInsideAnyPolygon(toCheck, object);
-
- final List nodesWithAddresses = nodesInside.stream().filter(Node.class::isInstance).map(Node.class::cast)
- .filter(node -> node.keySet().stream().anyMatch(str -> str.startsWith("addr:")))
- .filter(node -> !mergedNodes.contains(node)).collect(Collectors.toList());
-
- final List commandList = new ArrayList<>();
- if (nodesWithAddresses.size() == 1 && nodesWithAddresses.stream().allMatch(n -> n.getParentWays().isEmpty())) {
- String currentKey = null;
- Node node = nodesWithAddresses.get(0);
- mergedNodes.add(node);
- List sources = new ArrayList<>();
- try {
- // Remove the key to avoid the popup from utilsplugin2
- currentKey = object.get(KEY);
- sources.add(object.get(SOURCE));
- object.remove(KEY);
- object.remove(SOURCE);
- GuiHelper.runInEDTAndWait(
- () -> commandList.add(ReplaceGeometryUtils.buildUpgradeNodeCommand(node, object)));
- } finally {
- if (currentKey != null) {
- object.put(KEY, currentKey);
- }
- sources.add(node.get(SOURCE));
- sources.removeIf(Objects::isNull);
- sources = sources.stream().flatMap(source -> Stream.of(source.split(";", 0))).distinct()
- .filter(Objects::nonNull).sorted().collect(Collectors.toList());
- if (!sources.isEmpty()) {
- commandList.add(new ChangePropertyCommand(object, SOURCE, String.join(";", sources)));
- }
- }
- }
- return commandList;
- }
-
- @Override
- public boolean allowUndo() {
- return false;
- }
-
- @Override
- public boolean keyShouldNotExistInOSM() {
- return false;
- }
-
- @Override
- public Collection> conflictedCommands() {
- return Collections.singleton(MergeBuildingAddress.class);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeBuildingAddress.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeBuildingAddress.java
deleted file mode 100644
index 322a7c0d..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeBuildingAddress.java
+++ /dev/null
@@ -1,171 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.DeleteCommand;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.BBox;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.IPrimitive;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
-import org.openstreetmap.josm.tools.Geometry;
-
-/**
- * Merge addresses with pre-existing buildings
- *
- * @author Taylor Smock
- *
- */
-public class MergeBuildingAddress extends AbstractConflationCommand {
- public static final String KEY = "addr:housenumber";
-
- public MergeBuildingAddress(DataSet data) {
- super(data);
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Merge added addresses with existing buildings");
- }
-
- @Override
- public Collection> getInterestedTypes() {
- return Collections.singleton(Node.class);
- }
-
- @Override
- public String getKey() {
- return KEY;
- }
-
- @Override
- public Command getRealCommand() {
- List commands = new ArrayList<>();
- if (MapWithAIPreferenceHelper.isMergeBuildingAddress()) {
- possiblyAffectedPrimitives.stream().filter(Node.class::isInstance).map(Node.class::cast)
- .filter(n -> n.hasKey(KEY))
- .forEach(n -> commands.addAll(mergeBuildingAddress(getAffectedDataSet(), n)));
- }
-
- Command returnCommand = null;
- if (commands.size() == 1) {
- returnCommand = commands.get(0);
- } else if (!commands.isEmpty()) {
- returnCommand = new SequenceCommand(getDescriptionText(), commands);
- }
- return returnCommand;
- }
-
- private Collection mergeBuildingAddress(DataSet affectedDataSet, Node node) {
- final List toCheck = new ArrayList<>();
- final BBox bbox = new BBox(node.lon(), node.lat(), 0.001);
- GuiHelper.runInEDTAndWait(() -> {
- toCheck.addAll(affectedDataSet.searchWays(bbox));
- toCheck.addAll(affectedDataSet.searchRelations(bbox));
- toCheck.addAll(affectedDataSet.searchNodes(bbox));
- });
- List possibleDuplicates = toCheck.stream().filter(prim -> !prim.isDeleted() && prim.hasTag(KEY))
- .filter(prim -> prim.get(KEY).equals(node.get(KEY)))
- .filter(prim -> !prim.equals(node) && !this.possiblyAffectedPrimitives.contains(prim))
- .collect(Collectors.toList());
- for (String tag : Arrays.asList("addr:street", "addr:unit", "addr:housenumber", "addr:housename")) {
- if (node.hasTag(tag)) {
- possibleDuplicates = possibleDuplicates.stream().filter(prim -> prim.hasTag(tag))
- .filter(prim -> prim.get(tag).equals(node.get(tag))).collect(Collectors.toList());
- }
- }
-
- List buildings = toCheck.stream().filter(prim -> prim.hasTag("building"))
- .filter(prim -> checkInside(node, prim)).collect(Collectors.toList());
-
- final List commandList = new ArrayList<>();
- List sources = new ArrayList<>();
- OsmPrimitive object = null;
- sources.add(node.get(MergeAddressBuildings.SOURCE));
- if (possibleDuplicates.size() == 1) {
- final ChangePropertyCommand changePropertyCommand = new ChangePropertyCommand(possibleDuplicates,
- node.getKeys());
- if (changePropertyCommand.getObjectsNumber() > 0) {
- commandList.add(changePropertyCommand);
- }
- commandList.add(DeleteCommand.delete(Collections.singleton(node)));
- object = possibleDuplicates.get(0);
- } else if (buildings.size() == 1 && getAddressPoints(buildings.get(0)).size() == 1) {
- commandList.add(new ChangePropertyCommand(buildings, node.getKeys()));
- commandList.add(DeleteCommand.delete(Collections.singleton(node)));
- object = buildings.get(0);
- }
- if (object != null) {
- sources.add(object.get(MergeAddressBuildings.SOURCE));
- }
-
- sources.removeIf(Objects::isNull);
- sources = sources.stream().flatMap(source -> Stream.of(source.split(";", 0))).distinct()
- .filter(Objects::nonNull).sorted().collect(Collectors.toList());
- if (!sources.isEmpty() && object != null) {
- commandList.add(new ChangePropertyCommand(object, MergeAddressBuildings.SOURCE, String.join(";", sources)));
- }
-
- return commandList;
- }
-
- private static Collection getAddressPoints(OsmPrimitive prim) {
- BBox bbox = prim.getBBox();
- final Collection searchCollection = new HashSet<>();
- searchCollection.addAll(prim.getDataSet().searchNodes(bbox));
- searchCollection.addAll(prim.getDataSet().searchWays(bbox));
- searchCollection.addAll(prim.getDataSet().searchRelations(bbox));
- return Geometry.filterInsideAnyPolygon(searchCollection, prim).stream().filter(p -> !p.isDeleted())
- .filter(Node.class::isInstance).map(Node.class::cast).filter(n -> n.hasTag(KEY))
- .collect(Collectors.toList());
- }
-
- /**
- * Check if the node is inside the other primitive
- *
- * @param node The node to check
- * @param prim The primitive that the node may be inside
- * @return true if the node is inside the primitive
- */
- private static boolean checkInside(Node node, OsmPrimitive prim) {
- if (prim instanceof Relation) {
- return !Geometry.filterInsideMultipolygon(Collections.singleton(node), (Relation) prim).isEmpty();
- } else if (prim instanceof Way) {
- return !Geometry.filterInsidePolygon(Collections.singletonList(node), (Way) prim).isEmpty();
- }
- return false;
- }
-
- @Override
- public boolean allowUndo() {
- return false;
- }
-
- @Override
- public boolean keyShouldNotExistInOSM() {
- return false;
- }
-
- @Override
- public Collection> conflictedCommands() {
- return Collections.singleton(MergeAddressBuildings.class);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeBuildingNodeCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeBuildingNodeCommand.java
deleted file mode 100644
index 48a57cbb..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeBuildingNodeCommand.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Relation;
-import org.openstreetmap.josm.data.osm.Way;
-
-import jakarta.annotation.Nullable;
-
-/**
- * This is similar to the ReplaceGeometryUtils.buildUpgradeNodeCommand method
- *
- * @author Taylor Smock
- *
- */
-public final class MergeBuildingNodeCommand {
- private MergeBuildingNodeCommand() {
- // Hide constructor
- }
-
- /**
- * Upgrade a node to a way or multipolygon
- *
- * @param subjectNode node to be replaced
- * @param referenceObject object with greater spatial quality
- * @return The command that updates the node to a way/relation
- */
- @Nullable
- public static Command buildUpgradeNodeCommand(Node subjectNode, OsmPrimitive referenceObject) {
- boolean keepNode = !subjectNode.isNew();
- if (keepNode) {
- getNewOrNoTagNode(referenceObject);
- }
- return null;
- }
-
- private static Node getNewOrNoTagNode(OsmPrimitive referenceObject) {
- List nodes;
- if (referenceObject instanceof Way way) {
- nodes = way.getNodes();
- } else if (referenceObject instanceof Relation relation) {
- nodes = relation.getMemberPrimitives().stream().flatMap(o -> {
- if (o instanceof Way way) {
- return way.getNodes().stream();
- } else if (o instanceof Node node) {
- return Stream.of(node);
- }
- return null;
- }).filter(Objects::nonNull).toList();
- } else if (referenceObject instanceof Node node) {
- nodes = Collections.singletonList(node);
- } else {
- throw new IllegalArgumentException(tr("Unknown OsmPrimitive type"));
- }
- return nodes.stream().filter(OsmPrimitive::isNew).findAny()
- .orElse(nodes.stream().filter(p -> !p.isTagged()).findAny().orElse(nodes.get(0)));
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeDuplicateWays.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeDuplicateWays.java
deleted file mode 100644
index 019067e3..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MergeDuplicateWays.java
+++ /dev/null
@@ -1,412 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.openstreetmap.josm.command.ChangeCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.DeleteCommand;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.Bounds;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Pair;
-
-import jakarta.annotation.Nonnull;
-import jakarta.annotation.Nullable;
-
-/**
- * Merge duplicate ways
- */
-public class MergeDuplicateWays extends Command {
- public static final String ORIG_ID = "orig_id";
-
- private final List ways;
-
- private final List commands;
- private Command command;
-
- private Bounds bound;
-
- /**
- * Merge duplicate ways
- *
- * @param data Merge all duplicate ways in the dataset
- */
- public MergeDuplicateWays(DataSet data) {
- this(data, null, null);
- }
-
- /**
- * Merge duplicate ways
- *
- * @param way1 The way to merge duplicates to
- */
- public MergeDuplicateWays(Way way1) {
- this(way1.getDataSet(), way1, null);
- }
-
- /**
- * Merge duplicate ways
- *
- * @param way1 The way to merge duplicates to
- * @param way2 The way to merge from
- */
- public MergeDuplicateWays(Way way1, Way way2) {
- this(way1.getDataSet(), way1, way2);
- }
-
- /**
- * Merge duplicate ways
- *
- * @param data The originating dataset
- * @param way1 The first way to check against
- * @param way2 The second way
- */
- public MergeDuplicateWays(DataSet data, Way way1, Way way2) {
- this(data, Stream.of(way1, way2).filter(Objects::nonNull).toList());
- }
-
- /**
- * Merge duplicate ways
- *
- * @param data The originating dataset
- * @param ways The ways to merge. If empty, the entire dataset will be checked.
- */
- public MergeDuplicateWays(DataSet data, List ways) {
- super(data);
- this.ways = ways.stream().filter(MergeDuplicateWays::nonDeletedWay).toList();
- this.commands = new ArrayList<>();
- }
-
- private static boolean nonDeletedWay(Way way) {
- return !way.isDeleted() && way.getNodes().stream().noneMatch(OsmPrimitive::isDeleted);
- }
-
- @Override
- public boolean executeCommand() {
- if (commands.isEmpty() || (command == null)) {
- if (ways.isEmpty()) {
- filterDataSet(getAffectedDataSet(), commands, bound);
- } else if (ways.size() == 1) {
- checkForDuplicateWays(ways.get(0), commands);
- } else {
- final var it = ways.iterator();
- var way1 = it.next();
- while (it.hasNext()) {
- final var way2 = it.next();
- final var tCommand = checkForDuplicateWays(way1, way2);
- if (tCommand != null) {
- commands.add(tCommand);
- tCommand.executeCommand();
- }
- way1 = way2;
- }
- }
- final List realCommands = commands.stream().filter(Objects::nonNull).distinct().toList();
- commands.clear();
- commands.addAll(realCommands);
- if (!commands.isEmpty() && (commands.size() != 1)) {
- command = new SequenceCommand(getDescriptionText(), commands);
- } else if (commands.size() == 1) {
- command = commands.get(0);
- }
- } else {
- command.executeCommand();
- }
- return true;
- }
-
- @Override
- public void undoCommand() {
- if (command != null) {
- command.undoCommand();
- }
- }
-
- /**
- * Set the bounds for the command. Must be called before execution.
- *
- * @param bound The boundary for the command
- */
- public void setBounds(Bounds bound) {
- this.bound = bound;
- }
-
- /**
- * Look for duplicates in the dataset
- *
- * @param dataSet The dataset to look through
- * @param commands The command list to add to
- * @param bound The bounds to look at, may be {@code null}
- */
- public static void filterDataSet(@Nonnull DataSet dataSet, @Nonnull List commands,
- @Nullable Bounds bound) {
- final List ways = (bound == null ? dataSet.getWays() : dataSet.searchWays(bound.toBBox())).stream()
- .filter(prim -> !prim.isIncomplete() && !prim.isDeleted())
- .collect(Collectors.toCollection(ArrayList::new));
- for (var i = 0; i < ways.size(); i++) {
- final var way1 = ways.get(i);
- final var nearbyWays = dataSet.searchWays(way1.getBBox()).stream().filter(MergeDuplicateWays::nonDeletedWay)
- .filter(w -> !Objects.equals(w, way1)).toList();
- for (final Way way2 : nearbyWays) {
- final var command = checkForDuplicateWays(way1, way2);
- final var deletedWays = new ArrayList();
- if (command != null) {
- commands.add(command);
- command.executeCommand();
- command.fillModifiedData(new ArrayList<>(), deletedWays, new ArrayList<>());
- if (!deletedWays.contains(way1) && !deletedWays.contains(way2)) {
- commands.add(command);
- }
- ways.remove(way2);
- }
- }
- }
- }
-
- /**
- * Check for ways that are (partial) duplicates, and if so merge them
- *
- * @param way A way to check
- * @param commands A list of commands to add to
- */
- public static void checkForDuplicateWays(Way way, List commands) {
- final Collection nearbyWays = way.getDataSet().searchWays(way.getBBox()).stream()
- .filter(MergeDuplicateWays::nonDeletedWay).filter(w -> !Objects.equals(w, way)).toList();
- for (final Way way2 : nearbyWays) {
- if (!way2.isDeleted()) {
- final var tCommand = checkForDuplicateWays(way, way2);
- if (tCommand != null) {
- commands.add(tCommand);
- tCommand.executeCommand();
- }
- }
- }
- }
-
- /**
- * Check if ways are (partial) duplicates, and if so create a command to merge
- * them
- *
- * @param way1 A way to check
- * @param way2 A way to check
- * @return non-null command if they are duplicate ways
- */
- public static Command checkForDuplicateWays(Way way1, Way way2) {
- Command returnCommand = null;
- final Map, Map> duplicateNodes = getDuplicateNodes(way1, way2);
- final Set, Map>> duplicateEntrySet = duplicateNodes.entrySet();
- final Set, Pair>> compressed = duplicateNodes.entrySet().stream()
- .map(entry -> new Pair<>(entry.getKey(),
- new Pair<>(entry.getValue().entrySet().iterator().next().getKey(),
- entry.getValue().entrySet().iterator().next().getValue())))
- .sorted(Comparator.comparingInt(pair -> pair.a.a)).collect(Collectors.toCollection(LinkedHashSet::new));
- if (compressed.stream().anyMatch(entry -> entry.a.b.isDeleted() || entry.b.b.isDeleted())) {
- Logging.error("Bad node");
- Logging.error("{0}", way1);
- Logging.error("{0}", way2);
- }
- if ((compressed.size() > 1) && duplicateEntrySet.stream().noneMatch(entry -> entry.getValue().size() > 1)) {
- final var initial = compressed.stream().map(entry -> entry.a.a).sorted().toList();
- final var after = compressed.stream().map(entry -> entry.b.a).sorted().toList();
- if (sorted(initial) && sorted(after)) {
- returnCommand = mergeWays(way1, way2, compressed);
- }
- } else if (compressed.isEmpty() && way1.hasKey(ORIG_ID) && way1.get(ORIG_ID).equals(way2.get(ORIG_ID))) {
- returnCommand = mergeWays(way1, way2, compressed);
- }
- return returnCommand;
- }
-
- /**
- * Merge ways with multiple common nodes
- *
- * @param way1 The way to keep
- * @param way2 The way to remove while moving its nodes to way1
- * @param compressed The duplicate nodes
- * @return A command to merge ways, null if not possible
- */
- public static Command mergeWays(Way way1, Way way2,
- Set, Pair>> compressed) {
- Command command = null;
- if ((compressed.size() > 1) || (way1.hasKey(ORIG_ID) && way1.get(ORIG_ID).equals(way2.get(ORIG_ID)))) {
- Set, Pair>> realSet = new LinkedHashSet<>(compressed);
- final boolean sameDirection = checkDirection(realSet);
- final List way2Nodes = way2.getNodes();
- if (!sameDirection) {
- Collections.reverse(way2Nodes);
- realSet = realSet.stream().map(pair -> {
- pair.b.a = way2Nodes.size() - pair.b.a - 1;
- return pair;
- }).collect(Collectors.toSet());
- }
- final int last = realSet.stream().mapToInt(pair -> pair.b.a).max().orElse(way2Nodes.size());
- final int first = realSet.stream().mapToInt(pair -> pair.b.a).min().orElse(0);
- final List before = new ArrayList<>();
- final List after = new ArrayList<>();
- for (final Node node : way2Nodes) {
- final int position = way2Nodes.indexOf(node);
- if (position < first) {
- before.add(node);
- } else if (position > last) {
- after.add(node);
- }
- }
- Collections.reverse(before);
- final var newWay = new Way(way1);
- List commands = new ArrayList<>();
- before.forEach(node -> newWay.addNode(0, node));
- after.forEach(newWay::addNode);
- if (newWay.getNodesCount() > 0) {
- final var changeCommand = new ChangeCommand(way1, newWay);
- commands.add(changeCommand);
- /*
- * This must be executed, otherwise the delete command will believe that way2
- * nodes don't belong to anything See Issue #46
- */
- changeCommand.executeCommand();
- commands.add(DeleteCommand.delete(Collections.singleton(way2), true, true));
- /*
- * Just to ensure that the dataset is consistent prior to the "real"
- * executeCommand
- */
- changeCommand.undoCommand();
- }
- if (commands.contains(null)) {
- commands = commands.stream().filter(Objects::nonNull).collect(Collectors.toList());
- }
- if (!commands.isEmpty()) {
- command = new SequenceCommand(tr("Merge ways"), commands);
- }
- }
- return command;
- }
-
- /**
- * Find a node's duplicate in a set of duplicates
- *
- * @param node The node to find in the set
- * @param compressed The set of node duplicates
- * @return The node that the param {@code node} duplicates
- */
- public static Node nodeInCompressed(Node node, Set, Pair>> compressed) {
- var returnNode = node;
- for (final var pair : compressed) {
- if (node.equals(pair.a.b)) {
- returnNode = pair.b.b;
- } else if (node.equals(pair.b.b)) {
- returnNode = pair.a.b;
- }
- if (!node.equals(returnNode)) {
- break;
- }
- }
- final var tReturnNode = returnNode;
- node.getKeys().forEach(tReturnNode::put);
- return returnNode;
- }
-
- /**
- * Check if the node pairs increment in the same direction (only checks first
- * two pairs), ensure that they are sorted with {@link #sorted}
- *
- * @param compressed The set of duplicate node/placement pairs
- * @return true if the node pairs increment in the same direction
- */
- public static boolean checkDirection(Set, Pair>> compressed) {
- final var iterator = compressed.iterator();
- var returnValue = false;
- if (compressed.size() > 1) {
- final var first = iterator.next();
- final var second = iterator.next();
- final var way1Forward = first.a.a < second.a.a;
- final var way2Forward = first.b.a < second.b.a;
- returnValue = way1Forward == way2Forward;
- }
- return returnValue;
- }
-
- /**
- * Check if a list is a consecutively increasing number list
- *
- * @param collection The list of integers
- * @return true if there are no gaps and it increases
- */
- public static boolean sorted(List collection) {
- var returnValue = true;
- if (collection.size() > 1) {
- Integer last = collection.get(0);
- for (var i = 1; i < collection.size(); i++) {
- final Integer next = collection.get(i);
- if ((next - last) != 1) {
- returnValue = false;
- break;
- }
- last = next;
- }
- }
- return returnValue;
- }
-
- /**
- * Get duplicate nodes from two ways
- *
- * @param way1 An initial way with nodes
- * @param way2 A way that may have duplicate nodes with way1
- * @return A map of node -> node(s) duplicates
- */
- public static Map, Map> getDuplicateNodes(Way way1, Way way2) {
- final var duplicateNodes = new LinkedHashMap, Map>();
- for (var j = 0; j < way1.getNodesCount(); j++) {
- final var origNode = way1.getNode(j);
- for (var k = 0; k < way2.getNodesCount(); k++) {
- final var possDupeNode = way2.getNode(k);
- if (origNode.equals(possDupeNode) || (origNode
- .greatCircleDistance(possDupeNode) < MapWithAIPreferenceHelper.getMaxNodeDistance())) {
- final var origNodePair = new Pair<>(j, origNode);
- final var dupeNodeMap = duplicateNodes.computeIfAbsent(origNodePair, ignored -> new HashMap<>());
- dupeNodeMap.put(k, possDupeNode);
- }
- }
- }
- return duplicateNodes;
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Merge ways");
- }
-
- @Override
- public void fillModifiedData(Collection modified, Collection deleted,
- Collection added) {
- for (final Command currentCommand : commands) {
- currentCommand.fillModifiedData(modified, deleted, added);
- }
- }
-
- @Override
- public Collection extends OsmPrimitive> getParticipatingPrimitives() {
- return commands.stream().flatMap(currentCommand -> currentCommand.getParticipatingPrimitives().stream())
- .collect(Collectors.toSet());
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MovePrimitiveDataSetCommand.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MovePrimitiveDataSetCommand.java
deleted file mode 100644
index 6fa6d895..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/MovePrimitiveDataSetCommand.java
+++ /dev/null
@@ -1,199 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-import static org.openstreetmap.josm.tools.I18n.trn;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.command.AddPrimitivesCommand;
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.DeleteCommand;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.PrimitiveData;
-import org.openstreetmap.josm.data.osm.visitor.MergeSourceBuildingVisitor;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
-import org.openstreetmap.josm.plugins.mapwithai.backend.GetDataRunnable;
-import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIDataUtils;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.bugreport.ReportedException;
-
-/**
- * Move primitives between datasets (*not* a copy)
- *
- * @author Taylor Smock
- */
-public class MovePrimitiveDataSetCommand extends Command {
- private Command command;
-
- /**
- * Move primitives from one dataset to another
- *
- * @param to The destination dataset
- * @param from The originating dataset
- * @param primitives The primitives to move
- */
- public MovePrimitiveDataSetCommand(DataSet to, DataSet from, Collection primitives) {
- super(to);
- if (from == null || to.isLocked() || from.isLocked() || to.equals(from)) {
- Logging.error("{0}: Cannot move primitives from {1} to {2}", MapWithAIPlugin.NAME, from, to);
- } else {
- command = moveCollection(from, to, primitives);
- }
- }
-
- /**
- * Move primitives from one dataset to another
- *
- * @param to The destination dataset
- * @param from The originating dataset
- * @param primitives The primitives to move
- * @param primitiveData A collection to be add the primitive data to (important
- * if any positive ids are available)
- */
- public MovePrimitiveDataSetCommand(DataSet to, DataSet from, Collection primitives,
- Collection primitiveData) {
- super(to);
- if (from == null || to.isLocked() || from.isLocked() || to.equals(from)) {
- Logging.error("{0}: Cannot move primitives from {1} to {2}", MapWithAIPlugin.NAME, from, to);
- } else {
- command = moveCollection(from, to, primitives, primitiveData);
- }
- }
-
- @Override
- public boolean executeCommand() {
- if (command != null) {
- // DeleteCommand is used when the MapWithAI layer has been deleted.
- if (command instanceof DeleteCommand) {
- command.undoCommand();
- } else {
- command.getAffectedDataSet().update(command::executeCommand);
- }
- }
- return true;
- }
-
- /**
- * Move primitives from one dataset to another
- *
- * @param to The receiving dataset
- * @param from The sending dataset
- * @param selection The primitives to move
- * @return The command that does the actual move
- */
- public static Command moveCollection(DataSet from, DataSet to, Collection selection) {
- return moveCollection(from, to, selection, new HashSet<>());
- }
-
- /**
- * Move primitives from one dataset to another
- *
- * @param to The receiving dataset
- * @param from The sending dataset
- * @param selection The primitives to move
- * @param primitiveData A collection to be add the primitive data to (important
- * if any positive ids are available)
- * @return The command that does the actual move
- */
- public static Command moveCollection(DataSet from, DataSet to, Collection selection,
- Collection primitiveData) {
- final var commands = new ArrayList();
-
- final var selected = from.getAllSelected();
- GuiHelper.runInEDTAndWait(() -> from.setSelected(selection));
- final var builder = new MergeSourceBuildingVisitor(from);
- final var hull = builder.build();
- GuiHelper.runInEDTAndWait(() -> from.setSelected(selected));
-
- final var primitiveAddData = hull.allPrimitives().stream().map(OsmPrimitive::save).toList();
- primitiveAddData.stream().map(data -> {
- if (data.getUniqueId() > 0) {
- // Don't do this with conn data?
- data.clearOsmMetadata();
- }
- return data;
- }).forEach(data -> data.remove(GetDataRunnable.MAPWITHAI_SOURCE_TAG_KEY));
- primitiveData.addAll(primitiveAddData);
-
- commands.add(new AddPrimitivesCommand(primitiveAddData,
- selection.stream().map(OsmPrimitive::save).collect(Collectors.toList()), to));
- final var removeKeyCommand = new ArrayList();
- final var fullSelection = new HashSet();
- MapWithAIDataUtils.addPrimitivesToCollection(fullSelection, selection);
- if (!fullSelection.isEmpty()) {
- CreateConnectionsCommand.getConflationCommands().forEach(clazz -> {
- try {
- removeKeyCommand.add(new ChangePropertyCommand(fullSelection,
- clazz.getConstructor(DataSet.class).newInstance(from).getKey(), null));
- } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
- | InvocationTargetException | NoSuchMethodException | SecurityException e) {
- Logging.error(e);
- }
- });
- }
- Command delete;
- if (!removeKeyCommand.isEmpty()) {
- final var sequence = new SequenceCommand("Temporary Command", removeKeyCommand);
- sequence.executeCommand(); // This *must* be executed for the delete command to get everything.
- delete = DeleteCommand.delete(selection, true, true);
- sequence.undoCommand();
- } else {
- delete = DeleteCommand.delete(selection, true, true);
- }
- commands.add(delete);
- commands.removeIf(Objects::isNull);
-
- if (!commands.isEmpty()) {
- return SequenceCommand.wrapIfNeeded(trn("Move {0} OSM Primitive between data sets",
- "Move {0} OSM Primitives between data sets", selection.size(), selection.size()), commands);
- }
- return null;
- }
-
- @Override
- public void undoCommand() {
- if (command != null) {
- try {
- if (command instanceof DeleteCommand) {
- command.executeCommand();
- } else {
- command.undoCommand();
- }
- } catch (ReportedException | AssertionError e) {
- if (!e.getMessage().contains("Primitive is of wrong data set for this command")) {
- throw e;
- }
- command = DeleteCommand.delete(command.getParticipatingPrimitives());
- command.executeCommand();
- }
- }
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Move OsmPrimitives between layers");
- }
-
- @Override
- public void fillModifiedData(Collection modified, Collection deleted,
- Collection added) {
- command.fillModifiedData(modified, deleted, added);
- }
-
- @Override
- public Collection extends OsmPrimitive> getParticipatingPrimitives() {
- return Optional.ofNullable(command).map(Command::getParticipatingPrimitives).orElseGet(Collections::emptySet);
- }
-}
diff --git a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/cleanup/MissingConnectionTags.java b/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/cleanup/MissingConnectionTags.java
deleted file mode 100644
index 3532815f..00000000
--- a/src/main/java/org/openstreetmap/josm/plugins/mapwithai/commands/cleanup/MissingConnectionTags.java
+++ /dev/null
@@ -1,329 +0,0 @@
-// License: GPL. For details, see LICENSE file.
-package org.openstreetmap.josm.plugins.mapwithai.commands.cleanup;
-
-import static org.openstreetmap.josm.tools.I18n.tr;
-
-import javax.swing.JOptionPane;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import org.openstreetmap.josm.actions.AutoScaleAction;
-import org.openstreetmap.josm.command.ChangePropertyCommand;
-import org.openstreetmap.josm.command.Command;
-import org.openstreetmap.josm.command.SequenceCommand;
-import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.data.osm.IPrimitive;
-import org.openstreetmap.josm.data.osm.Node;
-import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Way;
-import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper;
-import org.openstreetmap.josm.data.validation.TestError;
-import org.openstreetmap.josm.data.validation.tests.CrossingWays;
-import org.openstreetmap.josm.data.validation.tests.DuplicateNode;
-import org.openstreetmap.josm.data.validation.tests.UnconnectedWays;
-import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.layer.AbstractOsmDataLayer;
-import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
-import org.openstreetmap.josm.gui.util.GuiHelper;
-import org.openstreetmap.josm.plugins.mapwithai.commands.AbstractConflationCommand;
-import org.openstreetmap.josm.plugins.mapwithai.commands.CreateConnectionsCommand;
-import org.openstreetmap.josm.spi.preferences.Config;
-import org.openstreetmap.josm.tools.Geometry;
-import org.openstreetmap.josm.tools.Logging;
-import org.openstreetmap.josm.tools.Utils;
-
-/**
- * This checks for missing dupe tags, and asks the user if it is a duplicate
- *
- * @author Taylor Smock
- */
-public class MissingConnectionTags extends AbstractConflationCommand {
- private double precision;
- private static final String HIGHWAY = "highway";
-
- public MissingConnectionTags(DataSet data) {
- super(data);
- }
-
- @Override
- public String getDescriptionText() {
- return tr("Deduplicate nodes and connect ways");
- }
-
- @Override
- public Collection> getInterestedTypes() {
- return Collections.singleton(Way.class);
- }
-
- @Override
- public String getKey() {
- // For now, we assume that only highways are at issue.
- return HIGHWAY;
- }
-
- @Override
- public Command getRealCommand() {
- precision = Config.getPref().getDouble("validator.duplicatenodes.precision", 0.);
- // precision is in meters
- precision = precision == 0 ? 1 : precision;
- final var current = MainApplication.getLayerManager().getActiveLayer();
- final var ways = Utils.filteredCollection(possiblyAffectedPrimitives, Way.class);
- if (!ways.isEmpty()) {
- final var ds = this.getAffectedDataSet();
- MainApplication.getLayerManager().getLayersOfType(AbstractOsmDataLayer.class).stream()
- .filter(d -> ds.equals(d.getDataSet())).findAny()
- .ifPresent(toSwitch -> MainApplication.getLayerManager().setActiveLayer(toSwitch));
- }
- final var prefKey = "mapwithai.conflation.missingconflationtags";
-
- final var selection = getAffectedDataSet().getAllSelected();
- ConditionalOptionPaneUtil.startBulkOperation(prefKey);
- final var commands = new ArrayList();
- fixErrors(prefKey, commands, findDuplicateNodes(possiblyAffectedPrimitives));
- fixErrors(prefKey, commands, findCrossingWaysAtNodes(possiblyAffectedPrimitives));
- fixErrors(prefKey, commands, findUnconnectedWays(possiblyAffectedPrimitives));
- ConditionalOptionPaneUtil.endBulkOperation(prefKey);
- if (current != null) {
- MainApplication.getLayerManager().setActiveLayer(current);
- }
- GuiHelper.runInEDT(() -> getAffectedDataSet().setSelected(selection));
- if (commands.size() == 1) {
- return commands.iterator().next();
- } else if (!commands.isEmpty()) {
- return new SequenceCommand(tr("Perform missing conflation steps"), commands);
- }
- return null;
- }
-
- protected void fixErrors(String prefKey, Collection commands, Collection issues) {
- for (var issue : issues) {
- if (!issue.isFixable() || issue.getFix() == null
- || issue.getPrimitives().stream().anyMatch(IPrimitive::isDeleted)) {
- continue;
- }
- GuiHelper.runInEDT(() -> getAffectedDataSet().setSelected(issue.getPrimitives()));
- final var primitives = issue.getPrimitives();
- if (primitives.stream().noneMatch(Node.class::isInstance)) {
- AutoScaleAction.zoomTo(issue.getPrimitives());
- } else {
- AutoScaleAction.zoomTo(issue.getPrimitives().stream().filter(Node.class::isInstance)
- .map(Node.class::cast).collect(Collectors.toList()));
- }
- final var message = issue.getFix().getDescriptionText();
- boolean fixIt = ConditionalOptionPaneUtil.showConfirmationDialog(prefKey, MainApplication.getMainFrame(),
- message, tr("Possible missing conflation key"), JOptionPane.YES_NO_CANCEL_OPTION,
- JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_OPTION);
- if (fixIt) {
- final var command = issue.getFix();
- command.executeCommand();
- commands.add(command);
- }
- }
- }
-
- /**
- * Find nodes that may be missing a dupe tag
- *
- * @param possiblyAffectedPrimitives The primitives that may be affected
- * @return The issues
- */
- protected static Collection findDuplicateNodes(Collection possiblyAffectedPrimitives) {
- final var issues = new ArrayList();
- final var duplicateNodeTest = new DuplicateNode();
- for (var way : Utils.filteredCollection(possiblyAffectedPrimitives, Way.class)) {
- for (var node : way.getNodes()) {
- duplicateNodeTest.startTest(NullProgressMonitor.INSTANCE);
- final var searchBBox = node.getBBox();
- searchBBox.addPrimitive(node, 0.001);
- way.getDataSet().searchNodes(searchBBox).stream().filter(MissingConnectionTags::noConflationKey)
- .forEach(duplicateNodeTest::visit);
- duplicateNodeTest.endTest();
- final var dupeNodes = duplicateNodeTest.getErrors().stream()
- .filter(e -> e.getPrimitives().contains(node)).flatMap(e -> e.getPrimitives().stream())
- .distinct()
- .filter(p -> !p.isDeleted() && !p.equals(node) && noConflationKey(p) && p.getOsmId() > 0)
- .toList();
- if (duplicateNodeTest.getErrors().isEmpty() || dupeNodes.isEmpty()) {
- continue;
- }
- final var dupes = duplicateNodeTest.getErrors().stream().filter(e -> e.getPrimitives().contains(node))
- .flatMap(e -> e.getPrimitives().stream()).distinct()
- .filter(p -> !p.isDeleted() && !p.equals(node)).map(OsmPrimitive::getPrimitiveId)
- .map(Object::toString).toList();
-
- final var initial = duplicateNodeTest.getErrors().get(0);
- final var prims = new ArrayList(dupeNodes);
- prims.add(node);
- issues.add(TestError.builder(initial.getTester(), initial.getSeverity(), initial.getCode())
- .message(initial.getMessage()).primitives(prims)
- .fix(() -> new ChangePropertyCommand(node, "dupe", String.join(",", dupes))).build());
- duplicateNodeTest.clear();
- }
- }
- return issues;
- }
-
- /**
- * Find nodes that may be missing a conn tag
- *
- * @param possiblyAffectedPrimitives The primitives that may be affected
- * @return The issues found
- */
- protected Collection findCrossingWaysAtNodes(Collection possiblyAffectedPrimitives) {
- final var issues = new ArrayList();
- final var crossingWays = new CrossingWays.Ways();
- for (var way : Utils.filteredCollection(possiblyAffectedPrimitives, Way.class)) {
- final var seenFix = new HashSet();
- crossingWays.startTest(NullProgressMonitor.INSTANCE);
- way.getDataSet().searchWays(way.getBBox()).stream().filter(w -> w.hasKey(HIGHWAY))
- .forEach(crossingWays::visit);
- crossingWays.endTest();
- for (var error : crossingWays.getErrors()) {
- if (seenFix.containsAll(error.getPrimitives()) || error.getPrimitives().stream()
- .filter(Way.class::isInstance).map(Way.class::cast).noneMatch(w -> w.hasKey(HIGHWAY))) {
- continue;
- }
- final var fixError = TestError.builder(error.getTester(), error.getSeverity(), error.getCode())
- .primitives(error.getPrimitives()).fix(createIntersectionCommandSupplier(error, way, precision))
- .message(error.getMessage());
- seenFix.addAll(error.getPrimitives());
- issues.add(fixError.build());
- }
- crossingWays.clear();
- }
- return issues;
- }
-
- private static Supplier createIntersectionCommandSupplier(TestError error, Way way, double precision) {
- final var nodes = Geometry.addIntersections(error.getPrimitives().stream().filter(Way.class::isInstance)
- .map(Way.class::cast).filter(w -> w.hasKey(HIGHWAY)).collect(Collectors.toList()), false,
- new ArrayList<>());
- if (nodes.stream().filter(MissingConnectionTags::noConflationKey).filter(Objects::nonNull)
- .anyMatch(n -> way.getNodes().stream().filter(MissingConnectionTags::noConflationKey)
- .filter(Objects::nonNull).anyMatch(wn -> n.greatCircleDistance(wn) < precision))) {
- return () -> createIntersectionCommand(way,
- way.getNodes().stream().filter(MissingConnectionTags::noConflationKey)
- .filter(n1 -> nodes.stream().anyMatch(n2 -> Geometry.getDistance(n1, n2) < precision))
- .collect(Collectors.toList()),
- precision);
- }
- return null;
- }
-
- private static Command createIntersectionCommand(Way way, Collection intersectionNodes, double precision) {
- final var commands = new ArrayList();
- if (intersectionNodes.stream().anyMatch(way::containsNode)) {
- final var searchWays = way.getDataSet().searchWays(way.getBBox()).stream()
- .filter(w -> w != way && w.hasKey(HIGHWAY)).toList();
- for (var potential : searchWays) {
- for (var node : way.getNodes()) {
- final var command = createAddNodeCommand(potential, node, precision);
- if (command != null) {
- commands.add(command);
- }
- }
- }
- }
- if (commands.size() == 1) {
- return commands.iterator().next();
- } else if (!commands.isEmpty()) {
- return new SequenceCommand(tr("Create intersections"), commands);
- }
- return null;
- }
-
- private static Command createAddNodeCommand(Way way, Node node, double precision) {
- if (Geometry.getDistance(node, way) < precision) {
- final var seg = Geometry.getClosestWaySegment(way, node);
- final var prims = Arrays.asList(way, seg.getFirstNode(), seg.getSecondNode());
- if (prims.stream().allMatch(p -> p.getOsmId() > 0)) {
- return new ChangePropertyCommand(node, "conn",
- prims.stream().map(p -> p.getPrimitiveId().toString()).collect(Collectors.joining(",")));
- }
- }
- return null;
- }
-
- protected static Collection findUnconnectedWays(Collection possiblyAffectedPrimitives) {
- final var unconnectedWays = new UnconnectedWays.UnconnectedHighways();
- unconnectedWays.startTest(NullProgressMonitor.INSTANCE);
- unconnectedWays.visit(possiblyAffectedPrimitives);
- unconnectedWays.endTest();
- double p = Config.getPref().getDouble(
- ValidatorPrefHelper.PREFIX + "." + UnconnectedWays.class.getSimpleName() + ".node_way_distance", 10.0);
- // precision is in meters
- double precision = p == 0 ? 1 : p;
-
- final var primsAndChildren = possiblyAffectedPrimitives.stream().filter(Way.class::isInstance)
- .map(Way.class::cast).map(Way::getNodes).flatMap(List::stream)
- .filter(MissingConnectionTags::noConflationKey).collect(Collectors.toSet());
- final var issues = new ArrayList