├── .github ├── cache-sources-action │ └── action.yml ├── dependabot.yml └── workflows │ ├── docs.yml │ ├── docs_mlc_config.json │ ├── maven.yml │ ├── performance.yml │ ├── release.yml │ ├── snapshot.yml │ └── update-pr.yml ├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── eclipseCodeFormatter.xml └── vcs.xml ├── .mvn ├── jvm.config └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── eclipse-formatter.xml ├── mvnw ├── mvnw.cmd ├── pom.xml ├── scripts ├── build-release.sh ├── build.sh ├── check-doc-links.sh ├── check-monaco.sh ├── format.sh ├── regenerate-openmaptiles.sh ├── set-versions.sh └── test-release.sh ├── src ├── main │ └── java │ │ └── org │ │ └── openmaptiles │ │ ├── Generate.java │ │ ├── Layer.java │ │ ├── OpenMapTilesMain.java │ │ ├── OpenMapTilesProfile.java │ │ ├── addons │ │ └── ExtraLayers.java │ │ ├── generated │ │ ├── OpenMapTilesSchema.java │ │ └── Tables.java │ │ ├── layers │ │ ├── AerodromeLabel.java │ │ ├── Aeroway.java │ │ ├── Boundary.java │ │ ├── Building.java │ │ ├── Housenumber.java │ │ ├── Landcover.java │ │ ├── Landuse.java │ │ ├── MountainPeak.java │ │ ├── Park.java │ │ ├── Place.java │ │ ├── Poi.java │ │ ├── Transportation.java │ │ ├── TransportationName.java │ │ ├── Water.java │ │ ├── WaterName.java │ │ └── Waterway.java │ │ └── util │ │ ├── OmtLanguageUtils.java │ │ ├── Utils.java │ │ └── VerifyMonaco.java └── test │ └── java │ └── org │ └── openmaptiles │ ├── GenerateTest.java │ ├── OpenMapTilesProfileTest.java │ ├── OpenMapTilesTest.java │ ├── layers │ ├── AbstractLayerTest.java │ ├── AerodromeLabelTest.java │ ├── AerowayTest.java │ ├── BoundaryTest.java │ ├── BuildingTest.java │ ├── HousenumberTest.java │ ├── LandcoverTest.java │ ├── LanduseTest.java │ ├── MountainPeakTest.java │ ├── ParkTest.java │ ├── PlaceTest.java │ ├── PoiTest.java │ ├── TransportationTest.java │ ├── WaterNameTest.java │ ├── WaterTest.java │ └── WaterwayTest.java │ └── util │ ├── OmtLanguageUtilsTest.java │ └── VerifyMonacoTest.java └── submodule.pom.xml /.github/cache-sources-action/action.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions 2 | name: 'Cache data/sources' 3 | description: 'Save/restore data/sources cache' 4 | inputs: 5 | basedir: 6 | description: 'Base dir for computing file hash' 7 | required: false 8 | default: '' 9 | runs: 10 | using: 'composite' 11 | steps: 12 | - name: Get Date 13 | id: get-data 14 | run: | 15 | echo "::set-output name=hash::${{ hashFiles('**/OpenMapTilesMain.java') }}" 16 | echo "::set-output name=date::$(date -u "+%Y-%m-%d")" 17 | shell: bash 18 | working-directory: ${{ inputs.basedir }} 19 | - uses: actions/cache@v4 20 | with: 21 | path: data/sources 22 | key: data-sources-${{ steps.get-data.outputs.date }}-${{ steps.get-data.outputs.hash }} 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Configure dependabot automatic version upgrades 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: maven 7 | directory: "/" 8 | open-pull-requests-limit: 1 9 | schedule: 10 | interval: daily 11 | time: "04:30" 12 | timezone: America/New_York 13 | labels: 14 | - dependencies 15 | - package-ecosystem: github-actions 16 | directory: "/" 17 | open-pull-requests-limit: 1 18 | schedule: 19 | interval: daily 20 | time: "04:30" 21 | timezone: America/New_York 22 | labels: 23 | - dependencies 24 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | markdown-link-check: 11 | name: Broken Links 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | - name: Run link check 17 | uses: gaurav-nelson/github-action-markdown-link-check@1.0.17 18 | with: 19 | use-quiet-mode: 'no' 20 | use-verbose-mode: 'yes' 21 | config-file: '.github/workflows/docs_mlc_config.json' 22 | -------------------------------------------------------------------------------- /.github/workflows/docs_mlc_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "^http://localhost.*$" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: CI 5 | 6 | on: 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | # When spotless:apply fails, the error message is a bit cryptic, so try to make it obvious that 12 | # is the problem by putting the check into a standalone job 13 | lint: 14 | name: Check formatting 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 15 17 | steps: 18 | - name: Checkout this PR planetiler-openmaptiles repo 19 | uses: actions/checkout@v4 20 | - name: Set up JDK 21 21 | uses: actions/setup-java@v4 22 | with: 23 | java-version: 21 24 | distribution: 'temurin' 25 | cache: 'maven' 26 | - name: Ensure code formatted with mvn spotless:apply 27 | run: ./mvnw -DskipTests --batch-mode -no-transfer-progress spotless:check 28 | 29 | build: 30 | name: Java ${{ matrix.jdk }} / ${{ matrix.os }} ${{ matrix.args }} 31 | # Wait until after we check that you ran mvn spotless:apply, otherwise will fail with a cryptic error message 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | os: [ ubuntu-latest, macos-latest, windows-latest ] 36 | jdk: [ 21 ] 37 | runs-on: ${{ matrix.os }} 38 | timeout-minutes: 15 39 | steps: 40 | - name: Checkout this PR planetiler-openmaptiles repo 41 | uses: actions/checkout@v4 42 | - name: Set up JDK ${{ matrix.jdk }} 43 | uses: actions/setup-java@v4 44 | with: 45 | java-version: ${{ matrix.jdk }} 46 | distribution: 'temurin' 47 | cache: 'maven' 48 | # Skip spotless since that gets checked in a separate task 49 | - name: Build with mvnw (linux/mac) 50 | if: ${{ !contains(matrix.os, 'windows') }} 51 | run: ./mvnw ${{matrix.args}} -Dspotless.check.skip --batch-mode -no-transfer-progress verify jib:buildTar --file pom.xml 52 | - name: Build with mvnw.cmd (windows) 53 | if: ${{ contains(matrix.os, 'windows') }} 54 | run: mvnw.cmd ${{matrix.args}} -Dspotless.check.skip --batch-mode -no-transfer-progress verify jib:buildTar --file pom.xml 55 | shell: cmd 56 | 57 | regenerate: 58 | name: Regenerate 59 | runs-on: ubuntu-latest 60 | timeout-minutes: 15 61 | steps: 62 | - name: Checkout this PR planetiler-openmaptiles repo 63 | uses: actions/checkout@v4 64 | - name: Set up JDK 21 65 | uses: actions/setup-java@v4 66 | with: 67 | java-version: 21 68 | distribution: 'temurin' 69 | cache: 'maven' 70 | - run: ./scripts/regenerate-openmaptiles.sh 71 | # Skip spotless since that gets checked in a separate task 72 | - run: ./mvnw -DskipTests -Dspotless.check.skip --batch-mode -no-transfer-progress clean verify 73 | 74 | run: 75 | name: Build / Run 76 | runs-on: ubuntu-latest 77 | timeout-minutes: 15 78 | steps: 79 | - name: Checkout this PR planetiler-openmaptiles repo 80 | uses: actions/checkout@v4 81 | - name: Cache data/sources 82 | uses: ./.github/cache-sources-action 83 | - name: Set up JDK 84 | uses: actions/setup-java@v4 85 | with: 86 | java-version: 21 87 | distribution: 'temurin' 88 | cache: 'maven' 89 | - name: Build this branch 90 | run: ./mvnw -DskipTests -Dimage.version=CI_ONLY --batch-mode -no-transfer-progress package jib:dockerBuild --file pom.xml 91 | - name: 'Upload artifact' 92 | uses: actions/upload-artifact@v4 93 | with: 94 | name: planetiler-build 95 | path: target/*with-deps.jar 96 | - name: 'Download data (java)' 97 | run: java -jar target/*with-deps.jar --only-download --area=monaco 98 | - name: 'Download wikidata (java)' 99 | run: java -jar target/*with-deps.jar --only-fetch-wikidata --area=monaco 100 | - name: 'Verify build' 101 | run: ./scripts/test-release.sh CI_ONLY 102 | 103 | submodule: 104 | name: Planetiler submodule 105 | runs-on: ubuntu-latest 106 | timeout-minutes: 15 107 | steps: 108 | - name: Checkout parent Planetiler repo 109 | uses: actions/checkout@v4 110 | with: 111 | repository: onthegomap/planetiler 112 | ref: main 113 | path: planetiler 114 | - name: Checkout this PR planetiler-openmaptiles repo 115 | uses: actions/checkout@v4 116 | with: 117 | path: planetiler-openmaptiles 118 | - name: Move planetiler-openmaptiles into planetiler 119 | run: mv planetiler-openmaptiles planetiler 120 | - name: Cache data/sources 121 | uses: ./planetiler/planetiler-openmaptiles/.github/cache-sources-action 122 | - name: Set up JDK 123 | uses: actions/setup-java@v4 124 | with: 125 | java-version: 21 126 | distribution: 'temurin' 127 | cache: 'maven' 128 | - name: Build and test this branch 129 | working-directory: planetiler 130 | run: ./mvnw -Dimage.version=CI_ONLY --batch-mode -no-transfer-progress verify jib:dockerBuild --file pom.xml 131 | - name: 'Download data (java)' 132 | working-directory: planetiler 133 | run: java -jar planetiler-dist/target/*with-deps.jar --only-download --area=monaco 134 | - name: 'Download wikidata (java)' 135 | working-directory: planetiler 136 | run: java -jar planetiler-dist/target/*with-deps.jar --only-fetch-wikidata --area=monaco 137 | - name: 'Verify build' 138 | working-directory: planetiler 139 | run: ./scripts/test-release.sh CI_ONLY 140 | env: 141 | SKIP_EXAMPLE_PROJECT: true 142 | -------------------------------------------------------------------------------- /.github/workflows/performance.yml: -------------------------------------------------------------------------------- 1 | # This workflow builds a map using the base and branch commit of a PR and uploads 2 | # the logs as an artifact that update-pr.yml uses to add back as a comment. 3 | 4 | name: Performance 5 | 6 | on: 7 | pull_request: 8 | branches: [ main ] 9 | 10 | env: 11 | # For performance tests, run this branch against main with: 12 | AREA: rhode island 13 | RAM: 4g 14 | # Also pick up a good chunk of the atlantic ocean to catch any regressions in repeated tile performance 15 | # Omit to infer from .osm.pbf bounds 16 | BOUNDS_ARG: "--bounds=-74.07,21.34,-17.84,43.55" 17 | 18 | jobs: 19 | performance: 20 | name: Performance Test 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 20 23 | continue-on-error: true 24 | steps: 25 | - name: 'Cancel previous runs' 26 | uses: styfle/cancel-workflow-action@0.12.1 27 | with: 28 | access_token: ${{ github.token }} 29 | 30 | - name: 'Checkout PT-OMT PR branch' 31 | uses: actions/checkout@v4 32 | with: 33 | path: branch 34 | 35 | - name: 'Checkout PT-OMT base' 36 | uses: actions/checkout@v4 37 | with: 38 | path: base 39 | ref: ${{ github.event.pull_request.base.sha }} 40 | 41 | - name: 'Cache data/sources' 42 | uses: ./branch/.github/cache-sources-action 43 | with: 44 | basedir: branch 45 | - name: 'Set up JDK' 46 | uses: actions/setup-java@v4 47 | with: 48 | java-version: 21 49 | distribution: 'temurin' 50 | cache: 'maven' 51 | - uses: actions/setup-node@v4 52 | with: 53 | node-version: '14' 54 | - run: npm install -g strip-ansi-cli@3.0.2 55 | 56 | - name: 'Build branch' 57 | run: ./scripts/build.sh 58 | working-directory: branch 59 | - name: 'Build base' 60 | run: ./scripts/build.sh 61 | working-directory: base 62 | 63 | - name: 'Download data' 64 | run: | 65 | set -eo pipefail 66 | cp base/target/*with-deps.jar run.jar && java -jar run.jar --only-download --area="${{ env.AREA }}" 67 | cp branch/target/*with-deps.jar run.jar && java -jar run.jar --only-download --area="${{ env.AREA }}" 68 | 69 | - name: 'Store build info' 70 | run: | 71 | mkdir build-info 72 | echo "${{ github.event.pull_request.base.sha }}" > build-info/base_sha 73 | echo "${{ github.sha }}" > build-info/branch_sha 74 | echo "${{ github.event.number }}" > build-info/pull_request_number 75 | 76 | - name: 'Run branch' 77 | run: | 78 | rm -rf data/out.mbtiles data/tmp 79 | cp branch/target/*with-deps.jar run.jar 80 | java -Xms${{ env.RAM }} -Xmx${{ env.RAM }} -jar run.jar --area="${{ env.AREA }}" "${{ env.BOUNDS_ARG }}" --mbtiles=data/out.mbtiles 2>&1 | tee log 81 | ls -alh run.jar | tee -a log 82 | cat log | strip-ansi > build-info/branchlogs.txt 83 | - name: 'Run base' 84 | run: | 85 | rm -rf data/out.mbtiles data/tmp 86 | cp base/target/*with-deps.jar run.jar 87 | java -Xms${{ env.RAM }} -Xmx${{ env.RAM }} -jar run.jar --area="${{ env.AREA }}" "${{ env.BOUNDS_ARG }}" --mbtiles=data/out.mbtiles 2>&1 | tee log 88 | ls -alh run.jar | tee -a log 89 | cat log | strip-ansi > build-info/baselogs.txt 90 | 91 | - name: 'Upload build-info' 92 | uses: actions/upload-artifact@v4 93 | with: 94 | name: build-info 95 | path: ./build-info 96 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish a Release 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version: 6 | description: 'Version without leading "v" (1.0, 2.3.4, 0.1.0-pre1)' 7 | required: true 8 | default: '' 9 | image_tags: 10 | description: 'Extra docker image tags ("latest,test")' 11 | required: true 12 | default: 'latest,release' 13 | jobs: 14 | publish: 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 20 17 | permissions: 18 | contents: write 19 | packages: write 20 | steps: 21 | - name: Ensure version does not start with 'v' 22 | uses: actions/github-script@v7 23 | with: 24 | github-token: ${{ github.token }} 25 | script: | 26 | version = context.payload.inputs.version; 27 | if (/^v/.test(version)) throw new Error("Bad version number: " + version) 28 | - uses: actions/checkout@v4 29 | - name: Cache data/sources 30 | uses: ./.github/cache-sources-action 31 | - uses: actions/setup-java@v4 32 | with: 33 | java-version: '21' 34 | distribution: 'temurin' 35 | cache: 'maven' 36 | 37 | - name: Check tag does not exist yet 38 | run: if git rev-list "v${{ github.event.inputs.version }}"; then echo "Tag already exists. Aborting the release process."; exit 1; fi 39 | 40 | - run: ./scripts/set-versions.sh "${{ github.event.inputs.version }}" 41 | - run: ./scripts/build-release.sh 42 | - run: ./scripts/test-release.sh "${{ github.event.inputs.version }}" 43 | - name: Create tag 44 | uses: actions/github-script@v7 45 | with: 46 | github-token: ${{ github.token }} 47 | script: | 48 | github.rest.git.createRef({ 49 | owner: context.repo.owner, 50 | repo: context.repo.repo, 51 | ref: "refs/tags/v${{ github.event.inputs.version }}", 52 | sha: context.sha 53 | }) 54 | - run: mv target/*with-deps.jar planetiler-openmaptiles.jar 55 | - run: sha256sum planetiler-openmaptiles.jar > planetiler-openmaptiles.jar.sha256 56 | - run: md5sum planetiler-openmaptiles.jar > planetiler-openmaptiles.jar.md5 57 | - name: Create Release 58 | uses: softprops/action-gh-release@v2 59 | with: 60 | fail_on_unmatched_files: true 61 | tag_name: v${{ github.event.inputs.version }} 62 | draft: true 63 | files: | 64 | planetiler-openmaptiles.jar* 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | - name: Login to Docker Hub 68 | uses: docker/login-action@v3 69 | with: 70 | username: ${{ secrets.DOCKERHUB_USERNAME }} 71 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 72 | - name: 'Build and push to dockerhub' 73 | run: ./mvnw -B -ntp -Pjib-multi-arch -Djib.to.tags="latest" package jib:build --file pom.xml 74 | - run: sha256sum target/*with-deps.jar 75 | - run: md5sum target/*with-deps.jar 76 | - name: 'Upload artifact' 77 | uses: actions/upload-artifact@v4 78 | with: 79 | name: planetiler-build 80 | path: target/*with-deps.jar 81 | -------------------------------------------------------------------------------- /.github/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | # Publish a docker image on each commit to main 2 | name: Publish a Snapshot 3 | 4 | on: 5 | push: 6 | branches: [ main ] 7 | 8 | jobs: 9 | snapshot: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 10 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up JDK 15 | uses: actions/setup-java@v4 16 | with: 17 | java-version: 21 18 | distribution: 'temurin' 19 | cache: 'maven' 20 | - name: Login to Docker Hub 21 | uses: docker/login-action@v3 22 | with: 23 | username: ${{ secrets.DOCKERHUB_USERNAME }} 24 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 25 | - name: 'Build and push to dockerhub' 26 | run: ./mvnw -B -ntp -Pjib-multi-arch -Djib.to.tags="latest" package jib:build --file pom.xml 27 | - run: sha256sum target/*with-deps.jar 28 | - run: md5sum target/*with-deps.jar 29 | - name: 'Upload artifact' 30 | uses: actions/upload-artifact@v4 31 | with: 32 | name: planetiler-build 33 | path: target/*with-deps.jar 34 | -------------------------------------------------------------------------------- /.github/workflows/update-pr.yml: -------------------------------------------------------------------------------- 1 | # This workflow posts the result of a performance test back to the pull request as a comment. 2 | # Needs to be separate from CI because it has elevated privileges so only runs from main branch. 3 | 4 | name: Update PR 5 | 6 | on: 7 | workflow_run: 8 | workflows: [ "Performance" ] 9 | types: 10 | - completed 11 | 12 | jobs: 13 | updatepr: 14 | runs-on: ubuntu-latest 15 | continue-on-error: true 16 | if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} 17 | timeout-minutes: 5 18 | steps: 19 | # report status back to pull request 20 | - uses: haya14busa/action-workflow_run-status@v1 21 | - uses: actions/checkout@v4 22 | - name: 'Download branch build info' 23 | uses: dawidd6/action-download-artifact@v9 24 | with: 25 | workflow: ${{ github.event.workflow_run.workflow_id }} 26 | run_id: ${{ github.event.workflow_run.id }} 27 | name: build-info 28 | path: build-info 29 | - name: 'Get build info' 30 | id: build_info 31 | run: echo "::set-output name=pr_number::$(cat build-info/pull_request_number)" 32 | - name: 'Build comment-body' 33 | run: | 34 | cat build-info/branchlogs.txt | sed -n '/^.*Tile stats/,$p' > branchsummary.txt 35 | cat build-info/branchlogs.txt | sed -n '/^.*Exception in thread/,$p' >> branchsummary.txt 36 | cat build-info/baselogs.txt | sed -n '/^.*Tile stats:/,$p' > basesummary.txt 37 | cat build-info/baselogs.txt | sed -n '/^.*Exception in thread/,$p' >> basesummary.txt 38 | 39 | cat << EOF > comment-body.txt 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 54 | 60 | 61 |
Base $(cat build-info/base_sha)This Branch $(cat build-info/branch_sha)
49 | 50 | \`\`\` 51 | $(cat basesummary.txt) 52 | \`\`\` 53 | 55 | 56 | \`\`\` 57 | $(cat build-info/branchlogs.txt | sed -n '/^.*Tile stats:/,$p') 58 | \`\`\` 59 |
62 | 63 | https://github.com/openmaptiles/planetiler-openmaptiles/actions/runs/${{ github.event.workflow_run.id }} 64 | 65 |
ℹ️ Base Logs $(cat build-info/base_sha) 66 | 67 | \`\`\` 68 | $(cat build-info/baselogs.txt) 69 | \`\`\` 70 |
71 | 72 |
ℹ️ This Branch Logs $(cat build-info/branch_sha) 73 | 74 | \`\`\` 75 | $(cat build-info/branchlogs.txt) 76 | \`\`\` 77 |
78 | EOF 79 | 80 | - name: 'Dump comment body' 81 | run: cat comment-body.txt 82 | 83 | - uses: marocchino/sticky-pull-request-comment@v2 84 | with: 85 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 86 | path: comment-body.txt 87 | header: performance-tests 88 | number: ${{ steps.build_info.outputs.pr_number }} 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | target/ 4 | .flattened-pom.xml 5 | *.jar 6 | !.mvn/wrapper/*.jar 7 | *.log 8 | 9 | # idea 10 | */.idea 11 | .idea/* 12 | *.iml 13 | !.idea/codeStyles 14 | !.idea/vcs.xml 15 | !.idea/eclipseCodeFormatter.xml 16 | 17 | # eclipse 18 | .classpath 19 | .project 20 | .settings 21 | bin/ 22 | 23 | data/ 24 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/eclipseCodeFormatter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.mvn/jvm.config: -------------------------------------------------------------------------------- 1 | --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 2 | --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED 3 | --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED 4 | --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED 5 | --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED 6 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.net.*; 18 | import java.io.*; 19 | import java.nio.channels.*; 20 | import java.util.Properties; 21 | 22 | public class MavenWrapperDownloader { 23 | 24 | private static final String WRAPPER_VERSION = "0.5.6"; 25 | /** 26 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 27 | */ 28 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 29 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 30 | 31 | /** 32 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the default 33 | * one. 34 | */ 35 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 36 | ".mvn/wrapper/maven-wrapper.properties"; 37 | 38 | /** 39 | * Path where the maven-wrapper.jar will be saved to. 40 | */ 41 | private static final String MAVEN_WRAPPER_JAR_PATH = 42 | ".mvn/wrapper/maven-wrapper.jar"; 43 | 44 | /** 45 | * Name of the property which should be used to override the default download url for the wrapper. 46 | */ 47 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 48 | 49 | public static void main(String args[]) { 50 | System.out.println("- Downloader started"); 51 | File baseDirectory = new File(args[0]); 52 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 53 | 54 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 55 | // wrapperUrl parameter. 56 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 57 | String url = DEFAULT_DOWNLOAD_URL; 58 | if (mavenWrapperPropertyFile.exists()) { 59 | FileInputStream mavenWrapperPropertyFileInputStream = null; 60 | try { 61 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 62 | Properties mavenWrapperProperties = new Properties(); 63 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 64 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 65 | } catch (IOException e) { 66 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 67 | } finally { 68 | try { 69 | if (mavenWrapperPropertyFileInputStream != null) { 70 | mavenWrapperPropertyFileInputStream.close(); 71 | } 72 | } catch (IOException e) { 73 | // Ignore ... 74 | } 75 | } 76 | } 77 | System.out.println("- Downloading from: " + url); 78 | 79 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 80 | if (!outputFile.getParentFile().exists()) { 81 | if (!outputFile.getParentFile().mkdirs()) { 82 | System.out.println( 83 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 84 | } 85 | } 86 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 87 | try { 88 | downloadFileFromURL(url, outputFile); 89 | System.out.println("Done"); 90 | System.exit(0); 91 | } catch (Throwable e) { 92 | System.out.println("- Error downloading"); 93 | e.printStackTrace(); 94 | System.exit(1); 95 | } 96 | } 97 | 98 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 99 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 100 | String username = System.getenv("MVNW_USERNAME"); 101 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 102 | Authenticator.setDefault(new Authenticator() { 103 | @Override 104 | protected PasswordAuthentication getPasswordAuthentication() { 105 | return new PasswordAuthentication(username, password); 106 | } 107 | }); 108 | } 109 | URL website = new URL(urlString); 110 | ReadableByteChannel rbc; 111 | rbc = Channels.newChannel(website.openStream()); 112 | FileOutputStream fos = new FileOutputStream(destination); 113 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 114 | fos.close(); 115 | rbc.close(); 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openmaptiles/planetiler-openmaptiles/946e3bffddbd477407ec8fa4151abd80acdd5b90/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.format.settings.url": "eclipse-formatter.xml", 3 | "java.format.settings.profile": "Planetiler", 4 | "java.completion.importOrder": [ 5 | "#", 6 | "" 7 | ], 8 | "java.sources.organizeImports.staticStarThreshold": 5, 9 | "java.sources.organizeImports.starThreshold": 999, 10 | "java.saveActions.organizeImports": true 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors. 2 | All rights reserved. 3 | 4 | The vector tile schema has been developed by Klokan Technologies GmbH and 5 | was initially modelled after the cartography of the CARTO's Positron basemap 6 | with permission from CartoDB Inc. 7 | The vector tile schema has been refined and improved in cooperation with 8 | the Wikimedia Foundation and is heavily influenced by years of 9 | Paul Norman's experience of creating maps from OpenStreetMap data. 10 | 11 | # Code license: BSD 3-Clause License 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | 16 | * Redistributions of source code must retain the above copyright notice, this 17 | list of conditions and the following disclaimer. 18 | 19 | * Redistributions in binary form must reproduce the above copyright notice, 20 | this list of conditions and the following disclaimer in the documentation 21 | and/or other materials provided with the distribution. 22 | 23 | * Neither the name of the copyright holder nor the names of its 24 | contributors may be used to endorse or promote products derived from 25 | this software without specific prior written permission. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 30 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 31 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 32 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 33 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 34 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 35 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | 38 | # Design license: CC-BY 4.0 39 | 40 | The cartography and visual design features of the map tile schema (also known as 41 | the "look and feel" of the map) are licensed under the Creative Commons 42 | Attribution 4.0 license. 43 | To view a copy of the license, visit http://creativecommons.org/licenses/by/4.0/. 44 | 45 | Products or services using maps derived from OpenMapTiles schema need to visibly 46 | credit "OpenMapTiles.org" or reference "OpenMapTiles" with a link to 47 | http://openmaptiles.org/. 48 | 49 | For a browsable electronic map based on OpenMapTiles and OpenStreetMap data, the 50 | credit should appear in the corner of the map. For example: 51 | 52 | [© OpenMapTiles](http://openmaptiles.org/) [© OpenStreetMap contributors](http://www.openstreetmap.org/copyright) 53 | 54 | For printed and static maps a similar attribution should be made in a textual 55 | description near the image, in the same fashion as if you cite a photograph. 56 | 57 | Exceptions to OpenMapTiles attribution requirement can be in a written form granted 58 | by MapTiler (info@maptiler.com). 59 | The project contributors grant MapTiler AG the license to give such 60 | exceptions on a commercial basis. 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Planetiler OpenMapTiles Profile 2 | 3 | This OpenMapTiles profile for [Planetiler](https://github.com/onthegomap/planetiler) is based 4 | on [OpenMapTiles](https://github.com/openmaptiles/openmaptiles). 5 | 6 | ## How to run 7 | 8 | Using pre-built docker image: 9 | 10 | ```bash 11 | docker run -v "$(pwd)/data":/data openmaptiles/planetiler-openmaptiles:latest --force --download --area=monaco 12 | ``` 13 | 14 | Or to build from source, after [installing Java 21+](https://adoptium.net/installation.html): 15 | 16 | ```bash 17 | # Build the project (use mvnw.cmd on windows): 18 | ./mvnw clean package 19 | # Then run: 20 | java -jar target/*with-deps.jar --force --download --area=monaco 21 | ``` 22 | 23 | See [Planetiler README.md](https://github.com/onthegomap/planetiler/blob/main/README.md) for more description of the 24 | available options. 25 | 26 | ## Differences from OpenMapTiles 27 | 28 | - Road name abbreviations are not implemented yet in the `transportation_name` layer 29 | - `brunnel` tag is excluded from `transportation_name` layer to avoid breaking apart long `transportation_name` 30 | lines, to revert this behavior set `--transportation-name-brunnel=true` 31 | - `rank` field on `mountain_peak` linestrings only has 3 levels (1: has wikipedia page and name, 2: has name, 3: no name 32 | or wikipedia page or name) 33 | - Some line and polygon tolerances are different, can be tweaked with `--simplify-tolerance` parameter 34 | - For bigger bays whose label points show above Z9, centerline is used for Z9+ 35 | 36 | ## Customizing 37 | 38 | If you want to exclude layers or only include certain layers, then run the project 39 | with `--exclude-layers=poi,housenumber,...` or `--only-layers=water,transportation,...` command-line arguments. 40 | 41 | If you want to customize existing layers in OpenMapTiles, then fork this repo, find the appropriate class from 42 | the [layers package](src/main/java/org/openmaptiles/layers), and make a change to where it processes output features. 43 | 44 |
45 | 46 | Example adding an attribute to a built-in layer 47 | 48 | 49 | For example to copy over the name attribute from OpenStreetMap elements to the building layer, 50 | modify [Building.java](src/main/java/org/openmaptiles/layers/Building.java): 51 | 52 | ```diff 53 | @@ -166,6 +166,7 @@ public class Building implements 54 | .setAttrWithMinzoom(Fields.RENDER_MIN_HEIGHT, renderMinHeight, 14) 55 | .setAttrWithMinzoom(Fields.COLOUR, color, 14) 56 | .setAttrWithMinzoom(Fields.HIDE_3D, hide3d, 14) 57 | + .setAttrWithMinzoom("name", element.source().getTag("name"), 14) 58 | .setSortKey(renderHeight); 59 | if (mergeZ13Buildings) { 60 | feature 61 | ``` 62 | 63 |
64 | 65 | If you want to generate a mbtiles file with OpenMapTiles base layers plus some extra ones then fork this repo and: 66 | 67 | 1. Create a new class that implements the [`Layer` interface](src/main/java/org/openmaptiles/Layer.java) in 68 | the [addons package](src/main/java/org/openmaptiles/addons) and make the `public String name()` method return the ID 69 | of the new layer. 70 | 2. Make the new class implement interfaces from `OpenMapTilesProfile` to register handlers for elements from input 71 | sources. For example implement `OpenMapTilesProfile.OsmAllProcessor` to handle every OSM element from `processAllOsm` 72 | method. See the [built-in layers](src/main/java/org/openmaptiles/layers) for examples. 73 | 3. Create a new instance of that class from the [`ExtraLayers`](src/main/java/org/openmaptiles/addons/ExtraLayers.java) 74 | class. 75 | 76 |
77 | 78 | Custom layer example 79 | 80 | 81 | This layer would add a `power` layer to OpenMapTiles output with power lines: 82 | 83 | ```java 84 | package org.openmaptiles.addons; 85 | 86 | import com.onthegomap.planetiler.FeatureCollector; 87 | import com.onthegomap.planetiler.reader.SourceFeature; 88 | import org.openmaptiles.Layer; 89 | import org.openmaptiles.OpenMapTilesProfile; 90 | 91 | public class Power implements Layer, OpenMapTilesProfile.OsmAllProcessor { 92 | 93 | private static final String LAYER_NAME = "power"; 94 | 95 | @Override 96 | public String name() { 97 | return LAYER_NAME; 98 | } 99 | 100 | @Override 101 | public void processAllOsm(SourceFeature feature, FeatureCollector features) { 102 | if (feature.canBeLine() && feature.hasTag("power", "line")) { 103 | features.line("power") 104 | .setBufferPixels(4) 105 | .setMinZoom(6) 106 | .setAttr("class", "line"); 107 | } 108 | } 109 | } 110 | ``` 111 | 112 |
113 | 114 | If you think your custom layer or change to a built-in layer might be useful to others, consider opening a pull request 115 | to contribute it back to this repo. Any change that diverges from what is produced 116 | by https://github.com/openmaptiles/openmaptiles should be disabled by default, and enabled through a command-line 117 | argument that users can opt-into. For example, see how 118 | the [building layer](src/main/java/org/openmaptiles/layers/Building.java) exposes a `building_merge_z13` command-line 119 | argument to disable merging nearby buildings at z13. 120 | 121 | ## Code Layout 122 | 123 | [Generate.java](src/main/java/org/openmaptiles/Generate.java) generates code in 124 | the [generated](src/main/java/org/openmaptiles/generated) package from an OpenMapTiles tag in 125 | GitHub: 126 | 127 | - [OpenMapTilesSchema](src/main/java/org/openmaptiles/generated/OpenMapTilesSchema.java) 128 | contains an interface for each layer with constants for the name, attributes, and allowed values for each tag in that 129 | layer 130 | - [Tables](src/main/java/org/openmaptiles/generated/Tables.java) 131 | contains a record for each table that OpenMapTiles [imposm3](https://github.com/omniscale/imposm3) configuration 132 | generates (along with the tag-filtering expression) so layers can listen on instances of those records instead of 133 | doing the tag filtering and parsing themselves 134 | 135 | The [layers](src/main/java/org/openmaptiles/layers) package contains a port of the SQL logic to 136 | generate each layer from OpenMapTiles. Layers define how source features (or parsed imposm3 table rows) map to vector 137 | tile features, and logic for post-processing tile geometries. 138 | 139 | [OpenMapTilesProfile](src/main/java/org/openmaptiles/OpenMapTilesProfile.java) dispatches source 140 | features to layer handlers and merges the results. 141 | 142 | [OpenMapTilesMain](src/main/java/org/openmaptiles/OpenMapTilesMain.java) is the main driver that 143 | registers source data and output location. 144 | 145 | ## Regenerating Code 146 | 147 | To run `Generate.java`, 148 | use [scripts/regenerate-openmaptiles.sh](https://github.com/openmaptiles/planetiler-openmaptiles/blob/main/scripts/regenerate-openmaptiles.sh) 149 | script with the 150 | OpenMapTiles release tag: 151 | 152 | ```bash 153 | ./scripts/regenerate-openmaptiles.sh v3.15 154 | ``` 155 | 156 | Then follow the instructions it prints for reformatting generated code. 157 | 158 | If you want to regenerate from a different repository than the default openmaptiles, you can specify the url like this: 159 | 160 | ```bash 161 | ./scripts/regenerate-openmaptiles.sh v3.15 https://raw.githubusercontent.com/openmaptiles/openmaptiles/ 162 | ``` 163 | 164 | ## License 165 | 166 | All code in this repository is under the [BSD license](./LICENSE.md) and the cartography decisions encoded in the schema 167 | and SQL are licensed under [CC-BY](./LICENSE.md). 168 | 169 | Products or services using maps derived from OpenMapTiles schema need to **visibly credit "OpenMapTiles.org"** or 170 | **reference "OpenMapTiles"** with a link to https://openmaptiles.org/. Exceptions to attribution requirement can be granted 171 | on request. 172 | 173 | For a browsable electronic map based on OpenMapTiles and OpenStreetMap data, the 174 | credit should appear in the corner of the map. For example: 175 | 176 | [© OpenMapTiles](https://openmaptiles.org/) [© OpenStreetMap contributors](https://www.openstreetmap.org/copyright) 177 | 178 | For printed and static maps a similar attribution should be made in a textual 179 | description near the image, in the same fashion as if you cite a photograph. 180 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | UTF-8 10 | 21 11 | 21 12 | 0.9.0 13 | 5.12.2 14 | 15 | org.openmaptiles.OpenMapTilesMain 16 | ${project.version} 17 | openmaptiles/planetiler-openmaptiles:${image.version} 18 | package 19 | amd64 20 | linux 21 | 22 | 23 | org.openmaptiles 24 | planetiler-openmaptiles 25 | 3.15.1-SNAPSHOT 26 | 27 | OpenMapTiles Vector Tile Schema implementation for Planetiler tool 28 | 29 | 30 | 31 | 32 | osgeo 33 | OSGeo Release Repository 34 | https://repo.osgeo.org/repository/release/ 35 | 36 | false 37 | 38 | 39 | true 40 | 41 | 42 | 43 | 44 | nexus-snapshots 45 | Nexus SNAPSHOT Repository 46 | https://s01.oss.sonatype.org/content/repositories/snapshots/ 47 | 48 | true 49 | 50 | 51 | false 52 | 53 | 54 | 55 | 56 | 57 | 58 | com.onthegomap.planetiler 59 | planetiler-core 60 | ${planetiler.version} 61 | 62 | 63 | org.yaml 64 | snakeyaml 65 | 2.4 66 | 67 | 68 | org.commonmark 69 | commonmark 70 | 0.24.0 71 | 72 | 73 | 74 | 75 | com.onthegomap.planetiler 76 | planetiler-core 77 | ${planetiler.version} 78 | test-jar 79 | test 80 | 81 | 82 | 83 | org.junit.jupiter 84 | junit-jupiter-api 85 | ${junit.version} 86 | test 87 | 88 | 89 | 90 | org.junit.jupiter 91 | junit-jupiter-params 92 | ${junit.version} 93 | test 94 | 95 | 96 | 97 | 98 | scm:git:https://github.com/openmaptiles/planetiler-openmaptiles.git 99 | https://github.com/openmaptiles/planetiler-openmaptiles 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-surefire-plugin 107 | 3.5.3 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-failsafe-plugin 112 | 3.5.3 113 | 114 | 115 | 116 | com.diffplug.spotless 117 | spotless-maven-plugin 118 | 2.44.4 119 | 120 | 121 | 122 | 123 | 124 | 4.21.0 125 | eclipse-formatter.xml 126 | 127 | 128 | 129 | 130 | **/*.md 131 | 132 | 133 | **/target/** 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | check 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | org.apache.maven.plugins 151 | maven-enforcer-plugin 152 | 3.5.0 153 | 154 | 155 | enforce-java 156 | 157 | enforce 158 | 159 | 160 | 161 | 162 | 21 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | org.apache.maven.plugins 173 | maven-assembly-plugin 174 | 3.7.1 175 | 176 | 177 | com.onthegomap.planetiler 178 | planetiler-core 179 | ${planetiler.version} 180 | 181 | 182 | 183 | 184 | 185 | 186 | true 187 | 188 | 189 | ${mainClass} 190 | 191 | 192 | 193 | with-deps 194 | 195 | false 196 | 197 | 198 | 199 | 200 | make-assembly 201 | ${assembly-phase} 202 | 203 | single 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | org.codehaus.mojo 212 | buildnumber-maven-plugin 213 | 3.2.1 214 | 215 | 216 | validate 217 | 218 | create 219 | 220 | 221 | 222 | 223 | false 224 | false 225 | 226 | 227 | 228 | 229 | 230 | com.google.cloud.tools 231 | jib-maven-plugin 232 | 3.4.5 233 | 234 | 235 | 236 | eclipse-temurin:21-jre 237 | 238 | 239 | 240 | ${jib.platform-arch} 241 | ${jib.platform-os} 242 | 243 | 244 | 245 | 246 | ${image} 247 | 248 | 249 | 250 | 251 | https://github.com/openmaptiles/planetiler-openmaptiles 252 | 253 | 254 | ${mainClass} 255 | ${maven.build.timestamp} 256 | ${maven.build.timestamp} 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | jib-multi-arch 266 | 267 | 268 | 269 | com.google.cloud.tools 270 | jib-maven-plugin 271 | 272 | 273 | 274 | 275 | amd64 276 | linux 277 | 278 | 279 | arm64 280 | linux 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /scripts/build-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | ./mvnw -B -ntp install jib:dockerBuild 6 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | ./mvnw -DskipTests=true clean package 8 | -------------------------------------------------------------------------------- /scripts/check-doc-links.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | find . -name '*.md' -not -path '*/target/*' -print0 | xargs -I {} -n 1 -0 markdown-link-check --quiet --config .github/workflows/docs_mlc_config.json {} 8 | -------------------------------------------------------------------------------- /scripts/check-monaco.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | java -ea -cp target/*-with-deps.jar org.openmaptiles.util.VerifyMonaco $* 8 | -------------------------------------------------------------------------------- /scripts/format.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | ./mvnw spotless:apply 6 | -------------------------------------------------------------------------------- /scripts/regenerate-openmaptiles.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o pipefail 5 | set -o nounset 6 | 7 | # TODO: change to "v3.16" once that is released 8 | TAG="${1:-"master"}" 9 | echo "tag=${TAG}" 10 | 11 | BASE_URL="${2:-"https://raw.githubusercontent.com/openmaptiles/openmaptiles/"}" 12 | echo "base-url=${BASE_URL}" 13 | 14 | echo "Building..." 15 | ./mvnw -DskipTests=true package 16 | 17 | echo "Running..." 18 | java -cp target/*-with-deps.jar org.openmaptiles.Generate -tag="${TAG}" -base-url="${BASE_URL}" 19 | 20 | echo "Formatting..." 21 | ./scripts/format.sh 22 | -------------------------------------------------------------------------------- /scripts/set-versions.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | if (( $# != 1 )); then 6 | echo "Usage: set_versions.sh " >&2 7 | exit 1 8 | fi 9 | 10 | version="$1" 11 | 12 | ./mvnw -B -ntp versions:set versions:commit -DnewVersion="${version}" 13 | -------------------------------------------------------------------------------- /scripts/test-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | version="${1:-$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)}" 6 | 7 | echo "Test java build" 8 | echo "::group::OpenMapTiles monaco (java)" 9 | # use different numbers of threads to stress-test determinism check 10 | java -jar target/*with-deps.jar --download --area=monaco --output=data/java.mbtiles --threads=4 11 | ./scripts/check-monaco.sh data/java.mbtiles 12 | echo "::endgroup::" 13 | 14 | echo "::endgroup::" 15 | echo "::group::OpenMapTiles monaco (docker)" 16 | rm -f data/docker.mbtiles 17 | docker run -v "$(pwd)/data":/data openmaptiles/planetiler-openmaptiles:"${version}" --area=monaco --output=data/docker.mbtiles --threads=32 18 | ./scripts/check-monaco.sh data/docker.mbtiles 19 | echo "::endgroup::" 20 | 21 | echo "::group::Compare" 22 | java -cp target/*with-deps.jar com.onthegomap.planetiler.util.CompareArchives data/java.mbtiles data/docker.mbtiles 23 | echo "::endgroup::" -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/Layer.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles; 2 | 3 | import com.onthegomap.planetiler.ForwardingProfile; 4 | 5 | /** Interface for all vector tile layer implementations that {@link OpenMapTilesProfile} delegates to. */ 6 | public interface Layer extends 7 | ForwardingProfile.Handler, 8 | ForwardingProfile.HandlerForLayer {} 9 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/OpenMapTilesMain.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles; 2 | 3 | import com.onthegomap.planetiler.Planetiler; 4 | import com.onthegomap.planetiler.config.Arguments; 5 | import java.nio.file.Path; 6 | import org.openmaptiles.generated.OpenMapTilesSchema; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * Main entrypoint for generating a map using the OpenMapTiles schema. 12 | */ 13 | public class OpenMapTilesMain { 14 | 15 | private static final Logger LOGGER = LoggerFactory.getLogger(OpenMapTilesMain.class); 16 | 17 | public static void main(String[] args) throws Exception { 18 | run(Arguments.fromArgsOrConfigFile(args)); 19 | } 20 | 21 | static void run(Arguments arguments) throws Exception { 22 | Path dataDir = Path.of("data"); 23 | Path sourcesDir = arguments.file("download_dir", "download directory", dataDir.resolve("sources")); 24 | // use --area=... argument, AREA=... env var or area=... in config to set the region of the world to use 25 | // will be ignored if osm_path or osm_url are set 26 | String area = arguments.getString( 27 | "area", 28 | "name of the extract to download if osm_url/osm_path not specified (i.e. 'monaco' 'rhode island' 'australia' or 'planet')", 29 | "monaco" 30 | ); 31 | 32 | Planetiler.create(arguments) 33 | .setDefaultLanguages(OpenMapTilesSchema.LANGUAGES) 34 | .fetchWikidataNameTranslations(sourcesDir.resolve("wikidata_names.json")) 35 | // defer creation of the profile because it depends on data from the runner 36 | .setProfile(OpenMapTilesProfile::new) 37 | // override any of these with arguments: --osm_path=... or --osm_url=... 38 | // or OSM_PATH=... OSM_URL=... environmental argument 39 | // or osm_path=... osm_url=... in a config file 40 | .addShapefileSource("EPSG:3857", OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, 41 | sourcesDir.resolve("lake_centerline.shp.zip"), 42 | // upstream is at https://github.com/acalcutt/osm-lakelines/releases/download/latest/lake_centerline.shp.zip , 43 | // following is same URL as used in the OpenMapTiles (but SHP format), a mirror maintained by MapTiler 44 | "https://dev.maptiler.download/geodata/omt/lake_centerline.shp.zip") 45 | .addShapefileSource(OpenMapTilesProfile.WATER_POLYGON_SOURCE, 46 | sourcesDir.resolve("water-polygons-split-3857.zip"), 47 | "https://osmdata.openstreetmap.de/download/water-polygons-split-3857.zip") 48 | .addNaturalEarthSource(OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 49 | sourcesDir.resolve("natural_earth_vector.sqlite.zip"), 50 | // upstream is at https://naciscdn.org/naturalearth/packages/natural_earth_vector.sqlite.zip , 51 | // following is same URL as used in the OpenMapTiles, a mirror maintained by MapTiler 52 | "https://dev.maptiler.download/geodata/omt/natural_earth_vector.sqlite.zip") 53 | .addOsmSource(OpenMapTilesProfile.OSM_SOURCE, 54 | sourcesDir.resolve(area.replaceAll("[^a-zA-Z]+", "_") + ".osm.pbf"), 55 | "planet".equalsIgnoreCase(area) ? ("aws:latest") : ("geofabrik:" + area)) 56 | // override with --mbtiles=... argument or MBTILES=... env var or mbtiles=... in a config file 57 | .setOutput("mbtiles", dataDir.resolve("output.mbtiles")) 58 | .run(); 59 | 60 | LOGGER.info(""" 61 | Acknowledgments 62 | Generated vector tiles are produced work of OpenStreetMap data. 63 | Such tiles are reusable under CC-BY license granted by OpenMapTiles team: 64 | - https://github.com/openmaptiles/openmaptiles/#license 65 | Maps made with these vector tiles must display a visible credit: 66 | - © OpenMapTiles © OpenStreetMap contributors 67 | Thanks to all free, open source software developers and Open Data Contributors! 68 | """); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/addons/ExtraLayers.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.addons; 2 | 3 | import com.onthegomap.planetiler.config.PlanetilerConfig; 4 | import com.onthegomap.planetiler.stats.Stats; 5 | import com.onthegomap.planetiler.util.Translations; 6 | import java.util.List; 7 | import org.openmaptiles.Layer; 8 | 9 | /** 10 | * Registry of extra custom layers that you can add to the openmaptiles schema. 11 | */ 12 | public class ExtraLayers { 13 | 14 | public static List create(Translations translations, PlanetilerConfig config, Stats stats) { 15 | return List.of( 16 | // Create classes that extend Layer interface in the addons package, then instantiate them here 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/AerodromeLabel.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import static org.openmaptiles.util.Utils.nullIfEmpty; 39 | import static org.openmaptiles.util.Utils.nullOrEmpty; 40 | 41 | import com.onthegomap.planetiler.FeatureCollector; 42 | import com.onthegomap.planetiler.config.PlanetilerConfig; 43 | import com.onthegomap.planetiler.expression.MultiExpression; 44 | import com.onthegomap.planetiler.stats.Stats; 45 | import com.onthegomap.planetiler.util.Translations; 46 | import org.openmaptiles.generated.OpenMapTilesSchema; 47 | import org.openmaptiles.generated.Tables; 48 | import org.openmaptiles.util.OmtLanguageUtils; 49 | import org.openmaptiles.util.Utils; 50 | 51 | /** 52 | * Defines the logic for generating map elements in the {@code aerodrome_label} layer from source features. 53 | *

54 | * This class is ported to Java from 55 | * OpenMapTiles 56 | * aerodrome_layer sql files. 57 | */ 58 | public class AerodromeLabel implements 59 | OpenMapTilesSchema.AerodromeLabel, 60 | Tables.OsmAerodromeLabelPoint.Handler { 61 | 62 | private final MultiExpression.Index classLookup; 63 | private final Translations translations; 64 | 65 | public AerodromeLabel(Translations translations, PlanetilerConfig config, Stats stats) { 66 | this.classLookup = FieldMappings.Class.index(); 67 | this.translations = translations; 68 | } 69 | 70 | @Override 71 | public void process(Tables.OsmAerodromeLabelPoint element, FeatureCollector features) { 72 | String clazz = classLookup.getOrElse(element.source(), FieldValues.CLASS_OTHER); 73 | boolean important = !nullOrEmpty(element.iata()) && FieldValues.CLASS_INTERNATIONAL.equals(clazz); 74 | features.centroid(LAYER_NAME) 75 | .setBufferPixels(BUFFER_SIZE) 76 | .setMinZoom(important ? 8 : 10) 77 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) 78 | .putAttrs(Utils.elevationTags(element.ele())) 79 | .setAttr(Fields.IATA, nullIfEmpty(element.iata())) 80 | .setAttr(Fields.ICAO, nullIfEmpty(element.icao())) 81 | .setAttr(Fields.CLASS, clazz); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/Aeroway.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import com.onthegomap.planetiler.FeatureCollector; 39 | import com.onthegomap.planetiler.config.PlanetilerConfig; 40 | import com.onthegomap.planetiler.stats.Stats; 41 | import com.onthegomap.planetiler.util.Translations; 42 | import org.openmaptiles.generated.OpenMapTilesSchema; 43 | import org.openmaptiles.generated.Tables; 44 | 45 | /** 46 | * Defines the logic for generating map elements in the {@code aeroway} layer from source features. 47 | *

48 | * This class is ported to Java from 49 | * OpenMapTiles aeroway sql files. 50 | */ 51 | public class Aeroway implements 52 | OpenMapTilesSchema.Aeroway, 53 | Tables.OsmAerowayLinestring.Handler, 54 | Tables.OsmAerowayPolygon.Handler, 55 | Tables.OsmAerowayPoint.Handler { 56 | 57 | public Aeroway(Translations translations, PlanetilerConfig config, Stats stats) {} 58 | 59 | @Override 60 | public void process(Tables.OsmAerowayPolygon element, FeatureCollector features) { 61 | features.polygon(LAYER_NAME) 62 | .setMinZoom(10) 63 | .setMinPixelSize(2) 64 | .setAttr(Fields.CLASS, element.aeroway()) 65 | .setAttr(Fields.REF, element.ref()); 66 | } 67 | 68 | @Override 69 | public void process(Tables.OsmAerowayLinestring element, FeatureCollector features) { 70 | features.line(LAYER_NAME) 71 | .setMinZoom(10) 72 | .setAttr(Fields.CLASS, element.aeroway()) 73 | .setAttr(Fields.REF, element.ref()); 74 | } 75 | 76 | @Override 77 | public void process(Tables.OsmAerowayPoint element, FeatureCollector features) { 78 | features.point(LAYER_NAME) 79 | .setMinZoom(14) 80 | .setAttr(Fields.CLASS, element.aeroway()) 81 | .setAttr(Fields.REF, element.ref()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/Building.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2023, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import static com.onthegomap.planetiler.util.MemoryEstimator.CLASS_HEADER_BYTES; 39 | import static com.onthegomap.planetiler.util.Parse.parseDoubleOrNull; 40 | import static java.util.Map.entry; 41 | import static org.openmaptiles.util.Utils.coalesce; 42 | 43 | import com.onthegomap.planetiler.FeatureCollector; 44 | import com.onthegomap.planetiler.FeatureMerge; 45 | import com.onthegomap.planetiler.ForwardingProfile; 46 | import com.onthegomap.planetiler.VectorTile; 47 | import com.onthegomap.planetiler.config.PlanetilerConfig; 48 | import com.onthegomap.planetiler.geo.GeometryException; 49 | import com.onthegomap.planetiler.reader.osm.OsmElement; 50 | import com.onthegomap.planetiler.reader.osm.OsmRelationInfo; 51 | import com.onthegomap.planetiler.stats.Stats; 52 | import com.onthegomap.planetiler.util.MemoryEstimator; 53 | import com.onthegomap.planetiler.util.Translations; 54 | import java.util.List; 55 | import java.util.Locale; 56 | import java.util.Map; 57 | import org.openmaptiles.generated.OpenMapTilesSchema; 58 | import org.openmaptiles.generated.Tables; 59 | 60 | /** 61 | * Defines the logic for generating map elements for buildings in the {@code building} layer from source features. 62 | *

63 | * This class is ported to Java from 64 | * OpenMapTiles building sql 65 | * files. 66 | */ 67 | public class Building implements 68 | OpenMapTilesSchema.Building, 69 | Tables.OsmBuildingPolygon.Handler, 70 | ForwardingProfile.LayerPostProcessor, 71 | ForwardingProfile.OsmRelationPreprocessor { 72 | 73 | /* 74 | * Emit all buildings from OSM data at z14. 75 | * 76 | * At z13, emit all buildings at process-time, but then at tile render-time, 77 | * merge buildings that are overlapping or almost touching into combined 78 | * buildings so that entire city blocks show up as a single building polygon. 79 | * 80 | * THIS IS VERY EXPENSIVE! Merging buildings at z13 adds about 50% to the 81 | * total map generation time. To disable it, set building_merge_z13 argument 82 | * to false. 83 | */ 84 | 85 | private static final Map MATERIAL_COLORS = Map.ofEntries( 86 | entry("cement_block", "#6a7880"), 87 | entry("brick", "#bd8161"), 88 | entry("plaster", "#dadbdb"), 89 | entry("wood", "#d48741"), 90 | entry("concrete", "#d3c2b0"), 91 | entry("metal", "#b7b1a6"), 92 | entry("stone", "#b4a995"), 93 | entry("mud", "#9d8b75"), 94 | entry("steel", "#b7b1a6"), // same as metal 95 | entry("glass", "#5a81a0"), 96 | entry("traditional", "#bd8161"), // same as brick 97 | entry("masonry", "#bd8161"), // same as brick 98 | entry("Brick", "#bd8161"), // same as brick 99 | entry("tin", "#b7b1a6"), // same as metal 100 | entry("timber_framing", "#b3b0a9"), 101 | entry("sandstone", "#b4a995"), // same as stone 102 | entry("clay", "#9d8b75") // same as mud 103 | ); 104 | private final boolean mergeZ13Buildings; 105 | 106 | public Building(Translations translations, PlanetilerConfig config, Stats stats) { 107 | this.mergeZ13Buildings = config.arguments().getBoolean( 108 | "building_merge_z13", 109 | "building layer: merge nearby buildings at z13", 110 | true 111 | ); 112 | } 113 | 114 | @Override 115 | public List preprocessOsmRelation(OsmElement.Relation relation) { 116 | if (relation.hasTag("type", "building")) { 117 | return List.of(new BuildingRelationInfo(relation.id())); 118 | } 119 | return null; 120 | } 121 | 122 | @Override 123 | public void process(Tables.OsmBuildingPolygon element, FeatureCollector features) { 124 | Boolean hide3d = null; 125 | var relations = element.source().relationInfo(BuildingRelationInfo.class); 126 | for (var relation : relations) { 127 | if ("outline".equals(relation.role())) { 128 | hide3d = true; 129 | break; 130 | } 131 | } 132 | 133 | String color = element.colour(); 134 | if (color == null && element.material() != null) { 135 | color = MATERIAL_COLORS.get(element.material()); 136 | } 137 | if (color != null) { 138 | color = color.toLowerCase(Locale.ROOT); 139 | } 140 | 141 | Double height = coalesce( 142 | parseDoubleOrNull(element.height()), 143 | parseDoubleOrNull(element.buildingheight()) 144 | ); 145 | Double minHeight = coalesce( 146 | parseDoubleOrNull(element.minHeight()), 147 | parseDoubleOrNull(element.buildingminHeight()) 148 | ); 149 | Double levels = coalesce( 150 | parseDoubleOrNull(element.levels()), 151 | parseDoubleOrNull(element.buildinglevels()) 152 | ); 153 | Double minLevels = coalesce( 154 | parseDoubleOrNull(element.minLevel()), 155 | parseDoubleOrNull(element.buildingminLevel()) 156 | ); 157 | 158 | int renderHeight = (int) Math.ceil(height != null ? height : levels != null ? (levels * 3.66) : 5); 159 | int renderMinHeight = (int) Math.floor(minHeight != null ? minHeight : minLevels != null ? (minLevels * 3.66) : 0); 160 | 161 | if (renderHeight < 3660 && renderMinHeight < 3660) { 162 | var feature = features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) 163 | .setMinZoom(13) 164 | .setMinPixelSize(2) 165 | .setAttrWithMinzoom(Fields.RENDER_HEIGHT, renderHeight, 14) 166 | .setAttrWithMinzoom(Fields.RENDER_MIN_HEIGHT, renderMinHeight, 14) 167 | .setAttrWithMinzoom(Fields.COLOUR, color, 14) 168 | .setAttrWithMinzoom(Fields.HIDE_3D, hide3d, 14) 169 | .setSortKey(renderHeight); 170 | if (mergeZ13Buildings) { 171 | feature 172 | .setMinPixelSize(0.1) 173 | .setPixelTolerance(0.25); 174 | } 175 | } 176 | } 177 | 178 | @Override 179 | public List postProcess(int zoom, 180 | List items) throws GeometryException { 181 | return (mergeZ13Buildings && zoom == 13) ? 182 | FeatureMerge.mergeNearbyPolygons(items, 4, 4, 0.5, 0.5) : 183 | // reduces the size of some heavy z14 tiles with many small buildings by 60% or more 184 | FeatureMerge.mergeMultiPolygon(items); 185 | } 186 | 187 | private record BuildingRelationInfo(long id) implements OsmRelationInfo { 188 | 189 | @Override 190 | public long estimateMemoryUsageBytes() { 191 | return CLASS_HEADER_BYTES + MemoryEstimator.estimateSizeLong(id); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/Housenumber.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import com.onthegomap.planetiler.FeatureCollector; 39 | import com.onthegomap.planetiler.FeatureMerge; 40 | import com.onthegomap.planetiler.ForwardingProfile; 41 | import com.onthegomap.planetiler.VectorTile; 42 | import com.onthegomap.planetiler.config.PlanetilerConfig; 43 | import com.onthegomap.planetiler.geo.GeometryException; 44 | import com.onthegomap.planetiler.stats.Stats; 45 | import com.onthegomap.planetiler.util.Translations; 46 | import java.util.Arrays; 47 | import java.util.Comparator; 48 | import java.util.List; 49 | import java.util.function.Predicate; 50 | import java.util.regex.Matcher; 51 | import java.util.regex.Pattern; 52 | import java.util.stream.Collectors; 53 | import org.openmaptiles.generated.OpenMapTilesSchema; 54 | import org.openmaptiles.generated.Tables; 55 | import org.openmaptiles.util.Utils; 56 | import org.slf4j.Logger; 57 | import org.slf4j.LoggerFactory; 58 | 59 | /** 60 | * Defines the logic for generating map elements in the {@code housenumber} layer from source features. 61 | *

62 | * This class is ported to Java from 63 | * OpenMapTiles housenumber sql 64 | * files. 65 | */ 66 | public class Housenumber implements 67 | OpenMapTilesSchema.Housenumber, 68 | Tables.OsmHousenumberPoint.Handler, 69 | ForwardingProfile.LayerPostProcessor { 70 | 71 | private static final Logger LOGGER = LoggerFactory.getLogger(Housenumber.class); 72 | private static final String OSM_SEPARATOR = ";"; 73 | private static final String DISPLAY_SEPARATOR = "–"; 74 | private static final Pattern NO_CONVERSION_PATTERN = Pattern.compile("[^0-9;]"); 75 | private static final String TEMP_PARTITION = "_partition"; 76 | private static final String TEMP_HAS_NAME = "_has_name"; 77 | private static final Comparator BY_TEMP_HAS_NAME = Comparator 78 | .comparing(i -> (Boolean) i.tags().get(TEMP_HAS_NAME), Boolean::compare); 79 | private final Stats stats; 80 | 81 | public Housenumber(Translations translations, PlanetilerConfig config, Stats stats) { 82 | this.stats = stats; 83 | } 84 | 85 | private static String displayHousenumberNonumeric(List numbers) { 86 | return numbers.getFirst() 87 | .concat(DISPLAY_SEPARATOR) 88 | .concat(numbers.getLast()); 89 | } 90 | 91 | protected static String displayHousenumber(String housenumber) { 92 | if (!housenumber.contains(OSM_SEPARATOR)) { 93 | return housenumber; 94 | } 95 | 96 | List numbers = Arrays.stream(housenumber.split(OSM_SEPARATOR)) 97 | .map(String::trim) 98 | .filter(Predicate.not(String::isEmpty)) 99 | .toList(); 100 | if (numbers.isEmpty()) { 101 | // not much to do with strange/invalid entries like "3;" or ";" etc. 102 | return housenumber; 103 | } 104 | 105 | Matcher matcher = NO_CONVERSION_PATTERN.matcher(housenumber); 106 | if (matcher.find()) { 107 | return displayHousenumberNonumeric(numbers); 108 | } 109 | 110 | // numeric display house number 111 | var statistics = numbers.stream() 112 | .collect(Collectors.summarizingLong(Long::parseUnsignedLong)); 113 | return String.valueOf(statistics.getMin()) 114 | .concat(DISPLAY_SEPARATOR) 115 | .concat(String.valueOf(statistics.getMax())); 116 | } 117 | 118 | @Override 119 | public void process(Tables.OsmHousenumberPoint element, FeatureCollector features) { 120 | String housenumber; 121 | try { 122 | housenumber = displayHousenumber(element.housenumber()); 123 | } catch (NumberFormatException e) { 124 | // should not be happening (thanks to NO_CONVERSION_PATTERN) but ... 125 | stats.dataError("housenumber_range"); 126 | LOGGER.warn("Failed to convert housenumber range: {}", element.housenumber()); 127 | housenumber = element.housenumber(); 128 | } 129 | 130 | String partition = Utils.coalesce(element.street(), "") 131 | .concat(Utils.coalesce(element.blockNumber(), "")) 132 | .concat(housenumber); 133 | Boolean hasName = element.hasName() == null ? Boolean.FALSE : !element.hasName().isEmpty(); 134 | 135 | features.centroidIfConvex(LAYER_NAME) 136 | .setBufferPixels(BUFFER_SIZE) 137 | .setAttr(Fields.HOUSENUMBER, housenumber) 138 | .setAttr(TEMP_PARTITION, partition) 139 | .setAttr(TEMP_HAS_NAME, hasName) 140 | .setMinZoom(14); 141 | } 142 | 143 | @Override 144 | public List postProcess(int zoom, List list) throws GeometryException { 145 | // remove duplicate house numbers, features without name tag are prioritized 146 | var items = list.stream() 147 | .collect(Collectors.groupingBy(f -> f.tags().get(TEMP_PARTITION))) 148 | .values().stream() 149 | .flatMap( 150 | g -> g.stream().min(BY_TEMP_HAS_NAME).stream() 151 | ) 152 | .toList(); 153 | 154 | // remove temporary attributes 155 | for (var item : items) { 156 | item.tags().remove(TEMP_HAS_NAME); 157 | item.tags().remove(TEMP_PARTITION); 158 | } 159 | 160 | // reduces the size of some heavy z14 tiles with many repeated housenumber values by 60% or more 161 | return FeatureMerge.mergeMultiPoint(items); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/Landcover.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2023, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import com.onthegomap.planetiler.FeatureCollector; 39 | import com.onthegomap.planetiler.FeatureMerge; 40 | import com.onthegomap.planetiler.ForwardingProfile; 41 | import com.onthegomap.planetiler.VectorTile; 42 | import com.onthegomap.planetiler.config.PlanetilerConfig; 43 | import com.onthegomap.planetiler.expression.MultiExpression; 44 | import com.onthegomap.planetiler.geo.GeometryException; 45 | import com.onthegomap.planetiler.reader.SourceFeature; 46 | import com.onthegomap.planetiler.stats.Stats; 47 | import com.onthegomap.planetiler.util.Translations; 48 | import com.onthegomap.planetiler.util.ZoomFunction; 49 | import java.util.ArrayList; 50 | import java.util.List; 51 | import java.util.Map; 52 | import java.util.Set; 53 | import org.openmaptiles.OpenMapTilesProfile; 54 | import org.openmaptiles.generated.OpenMapTilesSchema; 55 | import org.openmaptiles.generated.Tables; 56 | 57 | /** 58 | * Defines the logic for generating map elements for natural land cover polygons like ice, sand, and forest in the 59 | * {@code landcover} layer from source features. 60 | *

61 | * This class is ported to Java from 62 | * OpenMapTiles landcover sql 63 | * files. 64 | */ 65 | public class Landcover implements 66 | OpenMapTilesSchema.Landcover, 67 | OpenMapTilesProfile.NaturalEarthProcessor, 68 | Tables.OsmLandcoverPolygon.Handler, 69 | ForwardingProfile.LayerPostProcessor { 70 | 71 | /* 72 | * Large ice areas come from natural earth and the rest come from OpenStreetMap at higher zoom 73 | * levels. At render-time, postProcess() merges polygons into larger connected area based 74 | * on the number of points in the original area. Since postProcess() only has visibility into 75 | * features on a single tile, process() needs to pass the number of points the original feature 76 | * had through using a temporary "_numpoints" attribute. 77 | */ 78 | 79 | public static final ZoomFunction MIN_PIXEL_SIZE_THRESHOLDS = ZoomFunction.fromMaxZoomThresholds(Map.of( 80 | 13, 8, 81 | 10, 4, 82 | 9, 2 83 | )); 84 | private static final String TEMP_NUM_POINTS_ATTR = "_numpoints"; 85 | private static final Set WOOD_OR_FOREST = Set.of( 86 | FieldValues.SUBCLASS_WOOD, 87 | FieldValues.SUBCLASS_FOREST 88 | ); 89 | private final MultiExpression.Index classMapping; 90 | 91 | public Landcover(Translations translations, PlanetilerConfig config, Stats stats) { 92 | this.classMapping = FieldMappings.Class.index(); 93 | } 94 | 95 | private String getClassFromSubclass(String subclass) { 96 | return subclass == null ? null : classMapping.getOrElse(Map.of(Fields.SUBCLASS, subclass), null); 97 | } 98 | 99 | @Override 100 | public void processNaturalEarth(String table, SourceFeature feature, 101 | FeatureCollector features) { 102 | record LandcoverInfo(String subclass, int minzoom, int maxzoom) {} 103 | LandcoverInfo info = switch (table) { 104 | case "ne_110m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 0, 1); 105 | case "ne_50m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 2, 4); 106 | case "ne_10m_glaciated_areas" -> new LandcoverInfo(FieldValues.SUBCLASS_GLACIER, 5, 6); 107 | case "ne_50m_antarctic_ice_shelves_polys" -> new LandcoverInfo("ice_shelf", 2, 4); 108 | case "ne_10m_antarctic_ice_shelves_polys" -> new LandcoverInfo("ice_shelf", 5, 6); 109 | default -> null; 110 | }; 111 | if (info != null) { 112 | String clazz = getClassFromSubclass(info.subclass); 113 | if (clazz != null) { 114 | features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) 115 | .setAttr(Fields.CLASS, clazz) 116 | .setAttr(Fields.SUBCLASS, info.subclass) 117 | .setZoomRange(info.minzoom, info.maxzoom); 118 | } 119 | } 120 | } 121 | 122 | @Override 123 | public void process(Tables.OsmLandcoverPolygon element, FeatureCollector features) { 124 | String subclass = element.subclass(); 125 | String clazz = getClassFromSubclass(subclass); 126 | if (clazz != null) { 127 | features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) 128 | .setMinPixelSizeOverrides(MIN_PIXEL_SIZE_THRESHOLDS) 129 | // default is 0.1, this helps reduce size of some heavy z7-10 tiles 130 | .setPixelToleranceBelowZoom(10, 0.25) 131 | .setAttr(Fields.CLASS, clazz) 132 | .setAttr(Fields.SUBCLASS, subclass) 133 | .setNumPointsAttr(TEMP_NUM_POINTS_ATTR) 134 | .setMinZoom(7); 135 | } 136 | } 137 | 138 | @Override 139 | public List postProcess(int zoom, List items) throws GeometryException { 140 | if (zoom < 7 || zoom > 13) { 141 | for (var item : items) { 142 | item.tags().remove(TEMP_NUM_POINTS_ATTR); 143 | } 144 | return items; 145 | } else { // z7-13 146 | // merging only merges polygons with the same attributes, so use this temporary key 147 | // to separate features into layers that will be merged separately 148 | String tempGroupKey = "_group"; 149 | List result = new ArrayList<>(); 150 | List toMerge = new ArrayList<>(); 151 | for (var item : items) { 152 | Map attrs = item.attrs(); 153 | Object numPointsObj = attrs.remove(TEMP_NUM_POINTS_ATTR); 154 | Object subclassObj = attrs.get(Fields.SUBCLASS); 155 | if (numPointsObj instanceof Number num && subclassObj instanceof String subclass) { 156 | long numPoints = num.longValue(); 157 | if (zoom >= 10) { 158 | if (WOOD_OR_FOREST.contains(subclass) && numPoints < 300) { 159 | attrs.put(tempGroupKey, "<300"); 160 | toMerge.add(item); 161 | } else { // don't merge 162 | result.add(item); 163 | } 164 | } else if (zoom >= 8 && zoom <= 9) { 165 | if (WOOD_OR_FOREST.contains(subclass)) { 166 | attrs.put(tempGroupKey, numPoints < 300 ? "<300" : ">300"); 167 | toMerge.add(item); 168 | } else { // don't merge 169 | result.add(item); 170 | } 171 | } else { // zoom 7 172 | toMerge.add(item); 173 | } 174 | } else { 175 | result.add(item); 176 | } 177 | } 178 | var merged = FeatureMerge.mergeOverlappingPolygons(toMerge, 4); 179 | for (var item : merged) { 180 | item.tags().remove(tempGroupKey); 181 | } 182 | result.addAll(merged); 183 | return result; 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/Landuse.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import static org.openmaptiles.util.Utils.coalesce; 39 | import static org.openmaptiles.util.Utils.nullIfEmpty; 40 | 41 | import com.onthegomap.planetiler.FeatureCollector; 42 | import com.onthegomap.planetiler.FeatureMerge; 43 | import com.onthegomap.planetiler.ForwardingProfile; 44 | import com.onthegomap.planetiler.VectorTile; 45 | import com.onthegomap.planetiler.config.PlanetilerConfig; 46 | import com.onthegomap.planetiler.geo.GeometryException; 47 | import com.onthegomap.planetiler.reader.SourceFeature; 48 | import com.onthegomap.planetiler.stats.Stats; 49 | import com.onthegomap.planetiler.util.Parse; 50 | import com.onthegomap.planetiler.util.Translations; 51 | import com.onthegomap.planetiler.util.ZoomFunction; 52 | import java.util.ArrayList; 53 | import java.util.List; 54 | import java.util.Map; 55 | import java.util.Set; 56 | import java.util.TreeMap; 57 | import org.openmaptiles.OpenMapTilesProfile; 58 | import org.openmaptiles.generated.OpenMapTilesSchema; 59 | import org.openmaptiles.generated.Tables; 60 | 61 | /** 62 | * Defines the logic for generating map elements for man-made land use polygons like cemeteries, zoos, and hospitals in 63 | * the {@code landuse} layer from source features. 64 | *

65 | * This class is ported to Java from 66 | * OpenMapTiles landuse sql files. 67 | */ 68 | public class Landuse implements 69 | OpenMapTilesSchema.Landuse, 70 | OpenMapTilesProfile.NaturalEarthProcessor, 71 | ForwardingProfile.LayerPostProcessor, 72 | Tables.OsmLandusePolygon.Handler { 73 | 74 | private static final ZoomFunction MIN_PIXEL_SIZE_THRESHOLDS = ZoomFunction.fromMaxZoomThresholds(Map.of( 75 | 13, 4, 76 | 7, 2, 77 | 6, 1 78 | )); 79 | private static final TreeMap MINDIST_AND_BUFFER_SIZES = new TreeMap<>(Map.of( 80 | 5, 0.1, 81 | // there is quite huge jump between Z5:NE and Z6:OSM => bigger generalization needed to make the transition more smooth 82 | 6, 0.5, 83 | 7, 0.25, 84 | 8, 0.125, 85 | Integer.MAX_VALUE, 0.1 86 | )); 87 | private static final Set Z6_CLASSES = Set.of( 88 | FieldValues.CLASS_RESIDENTIAL, 89 | FieldValues.CLASS_SUBURB, 90 | FieldValues.CLASS_QUARTER, 91 | FieldValues.CLASS_NEIGHBOURHOOD 92 | ); 93 | 94 | public Landuse(Translations translations, PlanetilerConfig config, Stats stats) {} 95 | 96 | @Override 97 | public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) { 98 | if ("ne_50m_urban_areas".equals(table)) { 99 | Double scalerank = Parse.parseDoubleOrNull(feature.getTag("scalerank")); 100 | int minzoom = (scalerank != null && scalerank <= 2) ? 4 : 5; 101 | features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) 102 | .setAttr(Fields.CLASS, FieldValues.CLASS_RESIDENTIAL) 103 | .setZoomRange(minzoom, 5); 104 | } 105 | } 106 | 107 | @Override 108 | public void process(Tables.OsmLandusePolygon element, FeatureCollector features) { 109 | String clazz = coalesce( 110 | nullIfEmpty(element.landuse()), 111 | nullIfEmpty(element.amenity()), 112 | nullIfEmpty(element.leisure()), 113 | nullIfEmpty(element.tourism()), 114 | nullIfEmpty(element.place()), 115 | nullIfEmpty(element.waterway()) 116 | ); 117 | if (clazz != null) { 118 | if ("grave_yard".equals(clazz)) { 119 | clazz = FieldValues.CLASS_CEMETERY; 120 | } 121 | var feature = features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) 122 | .setAttr(Fields.CLASS, clazz) 123 | .setMinZoom(Z6_CLASSES.contains(clazz) ? 6 : 9); 124 | if (FieldValues.CLASS_RESIDENTIAL.equals(clazz)) { 125 | feature 126 | .setMinPixelSize(0.1) 127 | .setPixelTolerance(0.25); 128 | } else { 129 | feature 130 | .setMinPixelSizeOverrides(MIN_PIXEL_SIZE_THRESHOLDS); 131 | } 132 | } 133 | } 134 | 135 | @Override 136 | public List postProcess(int zoom, 137 | List items) throws GeometryException { 138 | List toMerge = new ArrayList<>(); 139 | List result = new ArrayList<>(); 140 | for (var item : items) { 141 | if (FieldValues.CLASS_RESIDENTIAL.equals(item.tags().get(Fields.CLASS))) { 142 | toMerge.add(item); 143 | } else { 144 | result.add(item); 145 | } 146 | } 147 | List merged; 148 | if (zoom <= 12) { 149 | double minDistAndBuffer = MINDIST_AND_BUFFER_SIZES.ceilingEntry(zoom).getValue(); 150 | merged = FeatureMerge.mergeNearbyPolygons(toMerge, 1, 1, minDistAndBuffer, minDistAndBuffer); 151 | } else { 152 | // reduces size of some heavy z13-14 tiles with lots of small polygons 153 | merged = FeatureMerge.mergeMultiPolygon(toMerge); 154 | } 155 | result.addAll(merged); 156 | return result; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/MountainPeak.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import static org.openmaptiles.util.Utils.elevationTags; 39 | import static org.openmaptiles.util.Utils.nullIfEmpty; 40 | 41 | import com.carrotsearch.hppc.LongIntMap; 42 | import com.onthegomap.planetiler.FeatureCollector; 43 | import com.onthegomap.planetiler.ForwardingProfile; 44 | import com.onthegomap.planetiler.VectorTile; 45 | import com.onthegomap.planetiler.collection.Hppc; 46 | import com.onthegomap.planetiler.config.PlanetilerConfig; 47 | import com.onthegomap.planetiler.geo.GeometryException; 48 | import com.onthegomap.planetiler.reader.SourceFeature; 49 | import com.onthegomap.planetiler.stats.Stats; 50 | import com.onthegomap.planetiler.util.Parse; 51 | import com.onthegomap.planetiler.util.Translations; 52 | import java.util.List; 53 | import java.util.concurrent.atomic.AtomicBoolean; 54 | import org.locationtech.jts.geom.Geometry; 55 | import org.locationtech.jts.geom.Point; 56 | import org.locationtech.jts.geom.prep.PreparedGeometry; 57 | import org.locationtech.jts.geom.prep.PreparedGeometryFactory; 58 | import org.openmaptiles.OpenMapTilesProfile; 59 | import org.openmaptiles.generated.OpenMapTilesSchema; 60 | import org.openmaptiles.generated.Tables; 61 | import org.openmaptiles.util.OmtLanguageUtils; 62 | import org.slf4j.Logger; 63 | import org.slf4j.LoggerFactory; 64 | 65 | /** 66 | * Defines the logic for generating map elements for mountain peak label points in the {@code mountain_peak} layer from 67 | * source features. 68 | *

69 | * This class is ported to Java from 70 | * OpenMapTiles mountain_peak 71 | * sql files. 72 | */ 73 | public class MountainPeak implements 74 | OpenMapTilesProfile.NaturalEarthProcessor, 75 | OpenMapTilesSchema.MountainPeak, 76 | Tables.OsmPeakPoint.Handler, 77 | Tables.OsmMountainLinestring.Handler, 78 | ForwardingProfile.LayerPostProcessor { 79 | 80 | /* 81 | * Mountain peaks come from OpenStreetMap data and are ranked by importance (based on if they 82 | * have a name or wikipedia page) then by elevation. Uses the "label grid" feature to limit 83 | * label density by only taking the top 5 most important mountain peaks within each 100x100px 84 | * square. 85 | */ 86 | private static final Logger LOGGER = LoggerFactory.getLogger(MountainPeak.class); 87 | 88 | private final Translations translations; 89 | private final Stats stats; 90 | // keep track of areas that prefer feet to meters to set customary_ft=1 (just U.S.) 91 | private PreparedGeometry unitedStates = null; 92 | private final AtomicBoolean loggedNoUS = new AtomicBoolean(false); 93 | 94 | public MountainPeak(Translations translations, PlanetilerConfig config, Stats stats) { 95 | this.translations = translations; 96 | this.stats = stats; 97 | } 98 | 99 | @Override 100 | public void processNaturalEarth(String table, SourceFeature feature, FeatureCollector features) { 101 | if ("ne_10m_admin_0_countries".equals(table) && feature.hasTag("iso_a2", "US")) { 102 | // multiple threads call this method concurrently, US polygon *should* only be found 103 | // once, but just to be safe synchronize updates to that field 104 | synchronized (this) { 105 | try { 106 | Geometry boundary = feature.polygon(); 107 | unitedStates = PreparedGeometryFactory.prepare(boundary); 108 | } catch (GeometryException e) { 109 | LOGGER.error("Failed to get United States Polygon for mountain_peak layer: " + e); 110 | } 111 | } 112 | } 113 | } 114 | 115 | @Override 116 | public void process(Tables.OsmPeakPoint element, FeatureCollector features) { 117 | Double meters = Parse.meters(element.ele()); 118 | if (meters != null && Math.abs(meters) < 10_000) { 119 | var feature = features.point(LAYER_NAME) 120 | .setAttr(Fields.CLASS, element.source().getTag("natural")) 121 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) 122 | .putAttrs(elevationTags(meters)) 123 | .setSortKeyDescending( 124 | meters.intValue() + 125 | (nullIfEmpty(element.wikipedia()) != null ? 10_000 : 0) + 126 | (nullIfEmpty(element.name()) != null ? 10_000 : 0) 127 | ) 128 | .setMinZoom(7) 129 | // need to use a larger buffer size to allow enough points through to not cut off 130 | // any label grid squares which could lead to inconsistent label ranks for a feature 131 | // in adjacent tiles. postProcess() will remove anything outside the desired buffer. 132 | .setBufferPixels(100) 133 | .setPointLabelGridSizeAndLimit(13, 100, 5); 134 | 135 | if (peakInAreaUsingFeet(element)) { 136 | feature.setAttr(Fields.CUSTOMARY_FT, 1); 137 | } 138 | } 139 | } 140 | 141 | @Override 142 | public void process(Tables.OsmMountainLinestring element, FeatureCollector features) { 143 | // TODO rank is approximate to sort important/named ridges before others, should switch to labelgrid for linestrings later 144 | int rank = 3 - 145 | (nullIfEmpty(element.wikipedia()) != null ? 1 : 0) - 146 | (nullIfEmpty(element.name()) != null ? 1 : 0); 147 | features.line(LAYER_NAME) 148 | .setAttr(Fields.CLASS, element.source().getTag("natural")) 149 | .setAttr(Fields.RANK, rank) 150 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) 151 | .setSortKey(rank) 152 | .setMinZoom(13) 153 | .setBufferPixels(100); 154 | } 155 | 156 | /** Returns true if {@code element} is a point in an area where feet are used insead of meters (the US). */ 157 | private boolean peakInAreaUsingFeet(Tables.OsmPeakPoint element) { 158 | if (unitedStates == null) { 159 | if (!loggedNoUS.get() && loggedNoUS.compareAndSet(false, true)) { 160 | LOGGER.warn("No US polygon for inferring mountain_peak customary_ft tag"); 161 | } 162 | } else { 163 | try { 164 | Geometry wayGeometry = element.source().worldGeometry(); 165 | return unitedStates.intersects(wayGeometry); 166 | } catch (GeometryException e) { 167 | e.log(stats, "omt_mountain_peak_us_test", 168 | "Unable to test mountain_peak against US polygon: " + element.source().id()); 169 | } 170 | } 171 | return false; 172 | } 173 | 174 | @Override 175 | public List postProcess(int zoom, List items) { 176 | LongIntMap groupCounts = Hppc.newLongIntHashMap(); 177 | for (int i = 0; i < items.size(); i++) { 178 | VectorTile.Feature feature = items.get(i); 179 | int gridrank = groupCounts.getOrDefault(feature.group(), 1); 180 | groupCounts.put(feature.group(), gridrank + 1); 181 | // now that we have accurate ranks, remove anything outside the desired buffer 182 | if (!insideTileBuffer(feature)) { 183 | items.set(i, null); 184 | } else if (!feature.tags().containsKey(Fields.RANK)) { 185 | feature.tags().put(Fields.RANK, gridrank); 186 | } 187 | } 188 | return items; 189 | } 190 | 191 | private static boolean insideTileBuffer(double xOrY) { 192 | return xOrY >= -BUFFER_SIZE && xOrY <= 256 + BUFFER_SIZE; 193 | } 194 | 195 | private boolean insideTileBuffer(VectorTile.Feature feature) { 196 | try { 197 | Geometry geom = feature.geometry().decode(); 198 | return !(geom instanceof Point point) || (insideTileBuffer(point.getX()) && insideTileBuffer(point.getY())); 199 | } catch (GeometryException e) { 200 | e.log(stats, "mountain_peak_decode_point", "Error decoding mountain peak point: " + feature.tags()); 201 | return false; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/layers/Park.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2024, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.layers; 37 | 38 | import static com.onthegomap.planetiler.collection.FeatureGroup.SORT_KEY_BITS; 39 | import static org.openmaptiles.util.Utils.coalesce; 40 | import static org.openmaptiles.util.Utils.nullIfEmpty; 41 | 42 | import com.carrotsearch.hppc.LongIntMap; 43 | import com.onthegomap.planetiler.FeatureCollector; 44 | import com.onthegomap.planetiler.FeatureMerge; 45 | import com.onthegomap.planetiler.ForwardingProfile; 46 | import com.onthegomap.planetiler.VectorTile; 47 | import com.onthegomap.planetiler.collection.Hppc; 48 | import com.onthegomap.planetiler.config.PlanetilerConfig; 49 | import com.onthegomap.planetiler.geo.GeoUtils; 50 | import com.onthegomap.planetiler.geo.GeometryException; 51 | import com.onthegomap.planetiler.geo.GeometryType; 52 | import com.onthegomap.planetiler.stats.Stats; 53 | import com.onthegomap.planetiler.util.SortKey; 54 | import com.onthegomap.planetiler.util.Translations; 55 | import java.util.List; 56 | import java.util.Locale; 57 | import org.openmaptiles.generated.OpenMapTilesSchema; 58 | import org.openmaptiles.generated.Tables; 59 | import org.openmaptiles.util.OmtLanguageUtils; 60 | 61 | /** 62 | * Defines the logic for generating map elements for designated parks polygons and their label points in the {@code 63 | * park} layer from source features. 64 | *

65 | * This class is ported to Java from 66 | * OpenMapTiles park sql files. 67 | */ 68 | public class Park implements 69 | OpenMapTilesSchema.Park, 70 | Tables.OsmParkPolygon.Handler, 71 | ForwardingProfile.LayerPostProcessor { 72 | 73 | // constants for determining the minimum zoom level for a park label based on its area 74 | private static final double WORLD_AREA_FOR_70K_SQUARE_METERS = 75 | Math.pow(GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d, 2); 76 | private static final double LOG2 = Math.log(2); 77 | private static final double SMALLEST_PARK_WORLD_AREA = Math.pow(4, -26); // 2^14 tiles, 2^12 pixels per tile 78 | 79 | private final Translations translations; 80 | private final Stats stats; 81 | 82 | public Park(Translations translations, PlanetilerConfig config, Stats stats) { 83 | this.stats = stats; 84 | this.translations = translations; 85 | } 86 | 87 | @Override 88 | public void process(Tables.OsmParkPolygon element, FeatureCollector features) { 89 | String protectionTitle = element.protectionTitle(); 90 | if (protectionTitle != null) { 91 | protectionTitle = protectionTitle.replace(' ', '_').toLowerCase(Locale.ROOT); 92 | } 93 | String clazz = coalesce( 94 | nullIfEmpty(protectionTitle), 95 | nullIfEmpty(element.boundary()), 96 | nullIfEmpty(element.leisure()) 97 | ); 98 | 99 | // park shape 100 | var outline = features.polygon(LAYER_NAME).setBufferPixels(BUFFER_SIZE) 101 | .setAttrWithMinzoom(Fields.CLASS, clazz, 5) 102 | .setMinPixelSize(2) 103 | .setMinZoom(4); 104 | 105 | // park name label point (if it has one) 106 | if (element.name() != null) { 107 | try { 108 | double area = element.source().area(); 109 | int minzoom = getMinZoomForArea(area); 110 | 111 | var names = OmtLanguageUtils.getNamesWithoutTranslations(element.source().tags()); 112 | 113 | outline.putAttrsWithMinzoom(names, 5); 114 | 115 | features.pointOnSurface(LAYER_NAME).setBufferPixels(256) 116 | .setAttr(Fields.CLASS, clazz) 117 | .putAttrs(names) 118 | .putAttrs(OmtLanguageUtils.getNames(element.source().tags(), translations)) 119 | .setPointLabelGridPixelSize(14, 100) 120 | .setSortKey(SortKey 121 | .orderByTruesFirst("national_park".equals(clazz)) 122 | .thenByTruesFirst(element.source().hasTag("wikipedia") || element.source().hasTag("wikidata")) 123 | .thenByLog(area, 1d, SMALLEST_PARK_WORLD_AREA, 1 << (SORT_KEY_BITS - 2) - 1) 124 | .get() 125 | ).setMinZoom(minzoom); 126 | } catch (GeometryException e) { 127 | e.log(stats, "omt_park_area", "Unable to get park area for " + element.source().id()); 128 | } 129 | } 130 | } 131 | 132 | private int getMinZoomForArea(double area) { 133 | // sql filter: area > 70000*2^(20-zoom_level) 134 | // simplifies to: zoom_level > 20 - log(area / 70000) / log(2) 135 | int minzoom = (int) Math.floor(20 - Math.log(area / WORLD_AREA_FOR_70K_SQUARE_METERS) / LOG2); 136 | minzoom = Math.clamp(minzoom, 5, 14); 137 | return minzoom; 138 | } 139 | 140 | @Override 141 | public List postProcess(int zoom, List items) throws GeometryException { 142 | // infer the "rank" attribute from point ordering within each label grid square 143 | LongIntMap counts = Hppc.newLongIntHashMap(); 144 | for (VectorTile.Feature feature : items) { 145 | if (feature.geometry().geomType() == GeometryType.POINT && feature.hasGroup()) { 146 | int count = counts.getOrDefault(feature.group(), 0) + 1; 147 | feature.tags().put("rank", count); 148 | counts.put(feature.group(), count); 149 | } 150 | } 151 | if (zoom <= 4) { 152 | items = FeatureMerge.mergeOverlappingPolygons(items, 0); 153 | } 154 | return items; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/util/OmtLanguageUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2021, MapTiler.com & OpenMapTiles contributors. 3 | All rights reserved. 4 | 5 | Code license: BSD 3-Clause License 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name of the copyright holder nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 22 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 25 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | Design license: CC-BY 4.0 33 | 34 | See https://github.com/openmaptiles/openmaptiles/blob/master/LICENSE.md for details on usage 35 | */ 36 | package org.openmaptiles.util; 37 | 38 | import static com.onthegomap.planetiler.util.LanguageUtils.*; 39 | import static org.openmaptiles.util.Utils.coalesce; 40 | 41 | import com.onthegomap.planetiler.util.LanguageUtils; 42 | import com.onthegomap.planetiler.util.Translations; 43 | import java.util.HashMap; 44 | import java.util.Map; 45 | import java.util.stream.Stream; 46 | 47 | /** 48 | * Utilities to extract common name fields (name, name_en, name_de, name:latin, name:nonlatin, name_int) that the 49 | * OpenMapTiles schema uses across any map element with a name. 50 | *

51 | * Ported from 52 | * openmaptiles-tools. 53 | */ 54 | public class OmtLanguageUtils { 55 | /** 56 | * Returns a map with default name attributes (name, name_en, name_de, name:latin, name:nonlatin, name_int) that every 57 | * element should have, derived from name, int_name, name:en, and name:de tags on the input element. 58 | * 59 | *

67 | */ 68 | public static Map getNamesWithoutTranslations(Map tags) { 69 | return getNames(tags, null); 70 | } 71 | 72 | /** 73 | * Returns a map with default name attributes that {@link #getNamesWithoutTranslations(Map)} adds, but also 74 | * translations for every language that {@code translations} is configured to handle. 75 | */ 76 | public static Map getNames(Map tags, Translations translations) { 77 | Map result = new HashMap<>(); 78 | 79 | String name = string(tags.get("name")); 80 | String intName = string(tags.get("int_name")); 81 | String nameEn = string(tags.get("name:en")); 82 | String nameDe = string(tags.get("name:de")); 83 | 84 | boolean isLatin = containsOnlyLatinCharacters(name); 85 | String latin = isLatin ? name : 86 | Stream 87 | .concat(Stream.of(nameEn, intName, nameDe), getAllNameTranslationsBesidesEnglishAndGerman(tags)) 88 | .filter(LanguageUtils::containsOnlyLatinCharacters) 89 | .findFirst().orElse(null); 90 | if (latin == null && translations != null && translations.getShouldTransliterate()) { 91 | latin = transliteratedName(tags); 92 | } 93 | String nonLatin = isLatin ? null : removeLatinCharacters(name); 94 | if (coalesce(nonLatin, "").equals(latin)) { 95 | nonLatin = null; 96 | } 97 | 98 | putIfNotEmpty(result, "name", name); 99 | putIfNotEmpty(result, "name_en", coalesce(nameEn, name)); 100 | putIfNotEmpty(result, "name_de", coalesce(nameDe, name, nameEn)); 101 | putIfNotEmpty(result, "name:latin", latin); 102 | putIfNotEmpty(result, "name:nonlatin", nonLatin); 103 | putIfNotEmpty(result, "name_int", coalesce( 104 | intName, 105 | nameEn, 106 | latin, 107 | name 108 | )); 109 | 110 | if (translations != null) { 111 | translations.addTranslations(result, tags); 112 | } 113 | 114 | return result; 115 | } 116 | 117 | public static String string(Object obj) { 118 | return nullIfEmpty(obj == null ? null : obj.toString()); 119 | } 120 | 121 | public static String transliteratedName(Map tags) { 122 | return Translations.transliterate(string(tags.get("name"))); 123 | } 124 | 125 | private static Stream getAllNameTranslationsBesidesEnglishAndGerman(Map tags) { 126 | return tags.entrySet().stream() 127 | .filter(e -> !EN_DE_NAME_KEYS.contains(e.getKey()) && VALID_NAME_TAGS.test(e.getKey())) 128 | .map(Map.Entry::getValue) 129 | .map(OmtLanguageUtils::string); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/util/Utils.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.util; 2 | 3 | import com.onthegomap.planetiler.util.Parse; 4 | import java.util.Map; 5 | 6 | /** 7 | * Common utilities for working with data and the OpenMapTiles schema in {@code layers} implementations. 8 | */ 9 | public class Utils { 10 | 11 | public static T coalesce(T a, T b) { 12 | return a != null ? a : b; 13 | } 14 | 15 | public static T coalesce(T a, T b, T c) { 16 | return a != null ? a : b != null ? b : c; 17 | } 18 | 19 | public static T coalesce(T a, T b, T c, T d) { 20 | return a != null ? a : b != null ? b : c != null ? c : d; 21 | } 22 | 23 | public static T coalesce(T a, T b, T c, T d, T e) { 24 | return a != null ? a : b != null ? b : c != null ? c : d != null ? d : e; 25 | } 26 | 27 | public static T coalesce(T a, T b, T c, T d, T e, T f) { 28 | return a != null ? a : b != null ? b : c != null ? c : d != null ? d : e != null ? e : f; 29 | } 30 | 31 | /** Boxes {@code a} into an {@link Integer}, or {@code null} if {@code a} is {@code nullValue}. */ 32 | public static Long nullIfLong(long a, long nullValue) { 33 | return a == nullValue ? null : a; 34 | } 35 | 36 | /** Boxes {@code a} into a {@link Long}, or {@code null} if {@code a} is {@code nullValue}. */ 37 | public static Integer nullIfInt(int a, int nullValue) { 38 | return a == nullValue ? null : a; 39 | } 40 | 41 | /** Returns {@code a}, or null if {@code a} is "". */ 42 | public static String nullIfEmpty(String a) { 43 | return (a == null || a.isEmpty()) ? null : a; 44 | } 45 | 46 | /** Returns true if {@code a} is null, or its {@link Object#toString()} value is "". */ 47 | public static boolean nullOrEmpty(Object a) { 48 | return a == null || a.toString().isEmpty(); 49 | } 50 | 51 | /** Returns a map with {@code ele} (meters) and {ele_ft} attributes from an elevation in meters. */ 52 | public static Map elevationTags(double meters) { 53 | return Map.of( 54 | "ele", (int) Math.round(meters), 55 | "ele_ft", (int) Math.round(meters * 3.2808399) 56 | ); 57 | } 58 | 59 | /** 60 | * Returns a map with {@code ele} (meters) and {ele_ft} attributes from an elevation string in meters, if {@code 61 | * meters} can be parsed as a valid number. 62 | */ 63 | public static Map elevationTags(String meters) { 64 | Double ele = Parse.meters(meters); 65 | return ele == null ? Map.of() : elevationTags(ele); 66 | } 67 | 68 | /** Returns "bridge" or "tunnel" string used for "brunnel" attribute by OpenMapTiles schema. */ 69 | public static String brunnel(boolean isBridge, boolean isTunnel) { 70 | return brunnel(isBridge, isTunnel, false); 71 | } 72 | 73 | /** Returns "bridge" or "tunnel" or "ford" string used for "brunnel" attribute by OpenMapTiles schema. */ 74 | public static String brunnel(boolean isBridge, boolean isTunnel, boolean isFord) { 75 | return isBridge ? "bridge" : isTunnel ? "tunnel" : isFord ? "ford" : null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/org/openmaptiles/util/VerifyMonaco.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.util; 2 | 3 | import com.onthegomap.planetiler.mbtiles.Mbtiles; 4 | import com.onthegomap.planetiler.mbtiles.Verify; 5 | import java.io.IOException; 6 | import java.nio.file.Path; 7 | import java.util.Map; 8 | import org.locationtech.jts.geom.Envelope; 9 | import org.locationtech.jts.geom.LineString; 10 | import org.locationtech.jts.geom.Point; 11 | import org.locationtech.jts.geom.Polygon; 12 | 13 | /** 14 | * A utility to check the contents of an mbtiles file generated for Monaco. 15 | */ 16 | public class VerifyMonaco { 17 | 18 | public static final Envelope MONACO_BOUNDS = new Envelope(7.40921, 7.44864, 43.72335, 43.75169); 19 | 20 | /** 21 | * Returns a verification result with a basic set of checks against an openmaptiles map built from an extract for 22 | * Monaco. 23 | */ 24 | public static Verify verify(Mbtiles mbtiles) { 25 | Verify verify = Verify.verify(mbtiles); 26 | verify.checkMinFeatureCount(MONACO_BOUNDS, "building", Map.of(), 13, 14, 100, Polygon.class); 27 | verify.checkMinFeatureCount(MONACO_BOUNDS, "transportation", Map.of(), 10, 14, 5, LineString.class); 28 | verify.checkMinFeatureCount(MONACO_BOUNDS, "landcover", Map.of( 29 | "class", "grass", 30 | "subclass", "park" 31 | ), 14, 10, Polygon.class); 32 | verify.checkMinFeatureCount(MONACO_BOUNDS, "water", Map.of("class", "ocean"), 0, 14, 1, Polygon.class); 33 | verify.checkMinFeatureCount(MONACO_BOUNDS, "place", Map.of("class", "country"), 2, 14, 1, Point.class); 34 | return verify; 35 | } 36 | 37 | public static void main(String[] args) throws IOException { 38 | try (var mbtiles = Mbtiles.newReadOnlyDatabase(Path.of(args[0]))) { 39 | var result = verify(mbtiles); 40 | result.print(); 41 | result.failIfErrors(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/GenerateTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles; 2 | 3 | import static com.onthegomap.planetiler.expression.Expression.*; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 6 | 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | import com.onthegomap.planetiler.expression.Expression; 9 | import com.onthegomap.planetiler.expression.MultiExpression; 10 | import java.util.LinkedHashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.stream.Stream; 14 | import org.junit.jupiter.api.DynamicTest; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.TestFactory; 17 | 18 | class GenerateTest { 19 | 20 | @Test 21 | void testParseSimple() { 22 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml(""" 23 | output: 24 | key: value 25 | key2: 26 | - value2 27 | - '%value3%' 28 | """)); 29 | assertEquals(MultiExpression.of(List.of( 30 | MultiExpression.entry("output", or( 31 | matchAny("key", "value"), 32 | matchAny("key2", "value2", "%value3%") 33 | )) 34 | )), parsed); 35 | } 36 | 37 | @Test 38 | void testParseAnd() { 39 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml(""" 40 | output: 41 | __AND__: 42 | key1: val1 43 | key2: val2 44 | """)); 45 | assertEquals(MultiExpression.of(List.of( 46 | MultiExpression.entry("output", and( 47 | matchAny("key1", "val1"), 48 | matchAny("key2", "val2") 49 | )) 50 | )), parsed); 51 | } 52 | 53 | @Test 54 | void testParseAndWithOthers() { 55 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml(""" 56 | output: 57 | - key0: val0 58 | - __AND__: 59 | key1: val1 60 | key2: val2 61 | """)); 62 | assertEquals(MultiExpression.of(List.of( 63 | MultiExpression.entry("output", or( 64 | matchAny("key0", "val0"), 65 | and( 66 | matchAny("key1", "val1"), 67 | matchAny("key2", "val2") 68 | ) 69 | )) 70 | )), parsed); 71 | } 72 | 73 | @Test 74 | void testParseAndContainingOthers() { 75 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml(""" 76 | output: 77 | __AND__: 78 | - key1: val1 79 | - __OR__: 80 | key2: val2 81 | key3: val3 82 | """)); 83 | assertEquals(MultiExpression.of(List.of( 84 | MultiExpression.entry("output", and( 85 | matchAny("key1", "val1"), 86 | or( 87 | matchAny("key2", "val2"), 88 | matchAny("key3", "val3") 89 | ) 90 | )) 91 | )), parsed); 92 | } 93 | 94 | @Test 95 | void testParseContainsKey() { 96 | MultiExpression parsed = Generate.generateFieldMapping(Generate.parseYaml(""" 97 | output: 98 | key1: val1 99 | key2: 100 | """)); 101 | assertEquals(MultiExpression.of(List.of( 102 | MultiExpression.entry("output", or( 103 | matchAny("key1", "val1"), 104 | matchField("key2") 105 | )) 106 | )), parsed); 107 | } 108 | 109 | @TestFactory 110 | Stream testParseImposm3Mapping() { 111 | record TestCase(String name, String mapping, String require, String reject, Expression expected) { 112 | 113 | TestCase(String mapping, Expression expected) { 114 | this(mapping, mapping, null, null, expected); 115 | } 116 | } 117 | return Stream.of( 118 | new TestCase( 119 | "key: val", matchAny("key", "val") 120 | ), 121 | new TestCase( 122 | "key: [val1, val2]", matchAny("key", "val1", "val2") 123 | ), 124 | new TestCase( 125 | "key: [\"__any__\"]", matchField("key") 126 | ), 127 | new TestCase("reject", 128 | "key: val", 129 | "mustkey: mustval", 130 | null, 131 | and( 132 | matchAny("key", "val"), 133 | matchAny("mustkey", "mustval") 134 | ) 135 | ), 136 | new TestCase("require", 137 | "key: val", 138 | null, 139 | "badkey: badval", 140 | and( 141 | matchAny("key", "val"), 142 | not(matchAny("badkey", "badval")) 143 | ) 144 | ), 145 | new TestCase("require and reject complex", 146 | """ 147 | key: val 148 | key2: 149 | - val1 150 | - val2 151 | """, 152 | """ 153 | mustkey: mustval 154 | mustkey2: 155 | - mustval1 156 | - mustval2 157 | """, 158 | """ 159 | notkey: notval 160 | notkey2: 161 | - notval1 162 | - notval2 163 | """, 164 | and( 165 | or( 166 | matchAny("key", "val"), 167 | matchAny("key2", "val1", "val2") 168 | ), 169 | matchAny("mustkey", "mustval"), 170 | matchAny("mustkey2", "mustval1", "mustval2"), 171 | not(matchAny("notkey", "notval")), 172 | not(matchAny("notkey2", "notval1", "notval2")) 173 | ) 174 | ) 175 | ).map(test -> dynamicTest(test.name, () -> { 176 | Expression parsed = Generate 177 | .parseImposm3MappingExpression("point", Generate.parseYaml(test.mapping), new Generate.Imposm3Filters( 178 | Generate.parseYaml(test.reject), 179 | Generate.parseYaml(test.require) 180 | )); 181 | assertEquals(test.expected, parsed.replace(matchType("point"), TRUE).simplify()); 182 | })); 183 | } 184 | 185 | @Test 186 | void testTypeMappingTopLevelType() { 187 | Expression parsed = Generate 188 | .parseImposm3MappingExpression("point", Generate.parseYaml(""" 189 | key: val 190 | """), new Generate.Imposm3Filters(null, null)); 191 | assertEquals(and( 192 | matchAny("key", "val"), 193 | matchType("point") 194 | ), parsed); 195 | } 196 | 197 | @Test 198 | void testTypeMappings() { 199 | Map props = new LinkedHashMap<>(); 200 | props.put("points", Generate.parseYaml(""" 201 | key: val 202 | """)); 203 | props.put("polygons", Generate.parseYaml(""" 204 | key2: val2 205 | """)); 206 | Expression parsed = Generate 207 | .parseImposm3MappingExpression(new Generate.Imposm3Table( 208 | "geometry", 209 | false, 210 | List.of(), 211 | null, 212 | null, 213 | props, 214 | List.of() 215 | )); 216 | assertEquals(or( 217 | and( 218 | matchAny("key", "val"), 219 | matchType("point") 220 | ), 221 | and( 222 | matchAny("key2", "val2"), 223 | matchType("polygon") 224 | ) 225 | ), parsed); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/OpenMapTilesProfileTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertFalse; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import com.onthegomap.planetiler.config.PlanetilerConfig; 7 | import com.onthegomap.planetiler.reader.osm.OsmElement; 8 | import com.onthegomap.planetiler.stats.Stats; 9 | import com.onthegomap.planetiler.util.Translations; 10 | import com.onthegomap.planetiler.util.Wikidata; 11 | import java.util.List; 12 | import org.junit.jupiter.api.Test; 13 | 14 | class OpenMapTilesProfileTest { 15 | 16 | private final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations(); 17 | private final Translations translations = Translations.defaultProvider(List.of("en", "es", "de")) 18 | .addFallbackTranslationProvider(wikidataTranslations); 19 | private final OpenMapTilesProfile profile = new OpenMapTilesProfile(translations, PlanetilerConfig.defaults(), 20 | Stats.inMemory()); 21 | 22 | @Test 23 | void testCaresAboutWikidata() { 24 | var node = new OsmElement.Node(1, 1, 1); 25 | node.setTag("aeroway", "gate"); 26 | assertTrue(profile.caresAboutWikidataTranslation(node)); 27 | 28 | node.setTag("aeroway", "other"); 29 | assertFalse(profile.caresAboutWikidataTranslation(node)); 30 | } 31 | 32 | @Test 33 | void testDoesntCareAboutWikidataForRoads() { 34 | var way = new OsmElement.Way(1); 35 | way.setTag("highway", "footway"); 36 | assertFalse(profile.caresAboutWikidataTranslation(way)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/OpenMapTilesTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.assertContains; 4 | import static com.onthegomap.planetiler.TestUtils.assertFeatureNear; 5 | import static com.onthegomap.planetiler.util.Gzip.gunzip; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 8 | 9 | import com.onthegomap.planetiler.TestUtils; 10 | import com.onthegomap.planetiler.VectorTile; 11 | import com.onthegomap.planetiler.archive.Tile; 12 | import com.onthegomap.planetiler.config.Arguments; 13 | import com.onthegomap.planetiler.mbtiles.Mbtiles; 14 | import com.onthegomap.planetiler.util.FileUtils; 15 | import java.io.IOException; 16 | import java.nio.file.Path; 17 | import java.util.Map; 18 | import java.util.Set; 19 | import java.util.stream.Stream; 20 | import org.junit.jupiter.api.AfterAll; 21 | import org.junit.jupiter.api.Assertions; 22 | import org.junit.jupiter.api.BeforeAll; 23 | import org.junit.jupiter.api.DynamicTest; 24 | import org.junit.jupiter.api.Test; 25 | import org.junit.jupiter.api.TestFactory; 26 | import org.junit.jupiter.api.Timeout; 27 | import org.junit.jupiter.api.io.TempDir; 28 | import org.locationtech.jts.geom.Geometry; 29 | import org.locationtech.jts.geom.LineString; 30 | import org.locationtech.jts.geom.Point; 31 | import org.locationtech.jts.geom.Polygon; 32 | import org.openmaptiles.util.VerifyMonaco; 33 | 34 | /** 35 | * End-to-end tests for OpenMapTiles generation. 36 | *

37 | * Generates an entire map for the smallest openstreetmap extract available (Monaco) and asserts that expected output 38 | * features exist 39 | */ 40 | class OpenMapTilesTest { 41 | 42 | @TempDir 43 | static Path tmpDir; 44 | private static Mbtiles mbtiles; 45 | 46 | @BeforeAll 47 | @Timeout(30) 48 | public static void runPlanetiler() throws Exception { 49 | Path dbPath = tmpDir.resolve("output.mbtiles"); 50 | var osmPath = TestUtils.extractPathToResource(tmpDir, "monaco-latest.osm.pbf"); 51 | var naturalEarthPath = TestUtils.extractPathToResource(tmpDir, "natural_earth_vector.sqlite.zip"); 52 | var waterPath = tmpDir.resolve("water"); 53 | // windows seems to have trouble closing zip file after reading from it, so extract first instead 54 | FileUtils.unzipResource("/water-polygons-split-3857.zip", waterPath); 55 | OpenMapTilesMain.run(Arguments.of( 56 | // Override input source locations 57 | "osm_path", osmPath, 58 | "natural_earth_path", naturalEarthPath, 59 | "water_polygons_path", waterPath, 60 | // no centerlines in monaco - so fake it out with an empty source 61 | "lake_centerlines_path", waterPath, 62 | 63 | // Override temp dir location 64 | "tmpdir", tmpDir.resolve("tmp"), 65 | 66 | // Override output location 67 | "mbtiles", dbPath 68 | )); 69 | 70 | mbtiles = Mbtiles.newReadOnlyDatabase(dbPath); 71 | } 72 | 73 | @AfterAll 74 | public static void close() throws IOException { 75 | mbtiles.close(); 76 | } 77 | 78 | @Test 79 | void testMetadata() { 80 | Map metadata = mbtiles.metadataTable().getAll(); 81 | assertEquals("OpenMapTiles", metadata.get("name")); 82 | assertEquals("0", metadata.get("minzoom")); 83 | assertEquals("14", metadata.get("maxzoom")); 84 | assertEquals("baselayer", metadata.get("type")); 85 | assertEquals("pbf", metadata.get("format")); 86 | assertEquals("7.40921,43.72335,7.44864,43.75169", metadata.get("bounds")); 87 | assertEquals("7.42892,43.73752,14", metadata.get("center")); 88 | assertContains("openmaptiles.org", metadata.get("description")); 89 | assertContains("openmaptiles.org", metadata.get("attribution")); 90 | assertContains("www.openstreetmap.org/copyright", metadata.get("attribution")); 91 | } 92 | 93 | @Test 94 | void ensureValidGeometries() throws Exception { 95 | Set parsedTiles = TestUtils.getTiles(mbtiles); 96 | for (var tileEntry : parsedTiles) { 97 | var decoded = VectorTile.decode(gunzip(tileEntry.bytes())); 98 | for (VectorTile.Feature feature : decoded) { 99 | TestUtils.validateGeometry(feature.geometry().decode()); 100 | } 101 | } 102 | } 103 | 104 | @Test 105 | void testContainsOceanPolyons() { 106 | assertFeatureNear(mbtiles, "water", Map.of( 107 | "class", "ocean" 108 | ), 7.4484, 43.70783, 0, 14); 109 | } 110 | 111 | @Test 112 | void testContainsCountryName() { 113 | assertFeatureNear(mbtiles, "place", Map.of( 114 | "class", "country", 115 | "iso_a2", "MC", 116 | "name", "Monaco" 117 | ), 7.42769, 43.73235, 2, 14); 118 | } 119 | 120 | @Test 121 | void testContainsSuburb() { 122 | assertFeatureNear(mbtiles, "place", Map.of( 123 | "name", "Les Moneghetti", 124 | "class", "suburb" 125 | ), 7.41746, 43.73638, 11, 14); 126 | } 127 | 128 | @Test 129 | void testContainsBuildings() { 130 | assertFeatureNear(mbtiles, "building", Map.of(), 7.41919, 43.73401, 13, 14); 131 | assertNumFeatures("building", Map.of(), 14, 1316, Polygon.class); 132 | assertNumFeatures("building", Map.of(), 13, 196, Polygon.class); 133 | } 134 | 135 | @Test 136 | void testContainsHousenumber() { 137 | assertFeatureNear(mbtiles, "housenumber", Map.of( 138 | "housenumber", "27" 139 | ), 7.42117, 43.73652, 14, 14); 140 | assertNumFeatures("housenumber", Map.of(), 14, 231, Point.class); 141 | } 142 | 143 | @Test 144 | void testBoundary() { 145 | assertFeatureNear(mbtiles, "boundary", Map.of( 146 | "admin_level", 2L, 147 | "maritime", 1L, 148 | "disputed", 0L 149 | ), 7.41884, 43.72396, 4, 14); 150 | } 151 | 152 | @Test 153 | void testAeroway() { 154 | assertNumFeatures("aeroway", Map.of( 155 | "class", "heliport" 156 | ), 14, 1, Polygon.class); 157 | assertNumFeatures("aeroway", Map.of( 158 | "class", "helipad" 159 | ), 14, 11, Polygon.class); 160 | } 161 | 162 | @Test 163 | void testLandcover() { 164 | assertNumFeatures("landcover", Map.of( 165 | "class", "grass", 166 | "subclass", "park" 167 | ), 14, 20, Polygon.class); 168 | assertNumFeatures("landcover", Map.of( 169 | "class", "grass", 170 | "subclass", "garden" 171 | ), 14, 33, Polygon.class); 172 | } 173 | 174 | @Test 175 | void testPoi() { 176 | assertNumFeatures("poi", Map.of( 177 | "class", "restaurant", 178 | "subclass", "restaurant" 179 | ), 14, 217, Point.class); 180 | assertNumFeatures("poi", Map.of( 181 | "class", "art_gallery", 182 | "subclass", "artwork" 183 | ), 14, 132, Point.class); 184 | } 185 | 186 | @Test 187 | void testLanduse() { 188 | assertNumFeatures("landuse", Map.of( 189 | "class", "residential" 190 | ), 14, 8, Polygon.class); 191 | assertNumFeatures("landuse", Map.of( 192 | "class", "hospital" 193 | ), 14, 4, Polygon.class); 194 | } 195 | 196 | @Test 197 | void testTransportation() { 198 | assertNumFeatures("transportation", Map.of( 199 | "class", "path", 200 | "subclass", "footway" 201 | ), 14, 756, LineString.class); 202 | assertNumFeatures("transportation", Map.of( 203 | "class", "primary" 204 | ), 14, 249, LineString.class); 205 | } 206 | 207 | @Test 208 | void testTransportationName() { 209 | assertNumFeatures("transportation_name", Map.of( 210 | "name", "Boulevard du Larvotto", 211 | "class", "primary" 212 | ), 14, 9, LineString.class); 213 | } 214 | 215 | @Test 216 | void testWaterway() { 217 | assertNumFeatures("waterway", Map.of( 218 | "class", "stream" 219 | ), 14, 6, LineString.class); 220 | } 221 | 222 | @TestFactory 223 | Stream testVerifyChecks() { 224 | return VerifyMonaco.verify(mbtiles).results().stream() 225 | .map(check -> dynamicTest(check.name(), () -> { 226 | check.error().ifPresent(Assertions::fail); 227 | })); 228 | } 229 | 230 | private static void assertNumFeatures(String layer, Map attrs, int zoom, 231 | int expected, Class clazz) { 232 | TestUtils.assertNumFeatures(mbtiles, layer, zoom, attrs, VerifyMonaco.MONACO_BOUNDS, expected, clazz); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/AbstractLayerTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.assertSubmap; 4 | import static com.onthegomap.planetiler.TestUtils.newLineString; 5 | import static com.onthegomap.planetiler.TestUtils.newPoint; 6 | import static com.onthegomap.planetiler.TestUtils.rectangle; 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.junit.jupiter.api.Assertions.fail; 9 | 10 | import com.onthegomap.planetiler.FeatureCollector; 11 | import com.onthegomap.planetiler.TestUtils; 12 | import com.onthegomap.planetiler.VectorTile; 13 | import com.onthegomap.planetiler.config.PlanetilerConfig; 14 | import com.onthegomap.planetiler.geo.GeoUtils; 15 | import com.onthegomap.planetiler.geo.GeometryException; 16 | import com.onthegomap.planetiler.reader.SimpleFeature; 17 | import com.onthegomap.planetiler.reader.SourceFeature; 18 | import com.onthegomap.planetiler.reader.osm.OsmReader; 19 | import com.onthegomap.planetiler.reader.osm.OsmRelationInfo; 20 | import com.onthegomap.planetiler.stats.Stats; 21 | import com.onthegomap.planetiler.util.Translations; 22 | import com.onthegomap.planetiler.util.Wikidata; 23 | import java.util.Arrays; 24 | import java.util.Comparator; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.stream.StreamSupport; 29 | import org.openmaptiles.OpenMapTilesProfile; 30 | import org.openmaptiles.util.Utils; 31 | 32 | public abstract class AbstractLayerTest { 33 | 34 | final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations(); 35 | final Translations translations = Translations.defaultProvider(List.of("en", "es", "de")) 36 | .addFallbackTranslationProvider(wikidataTranslations); 37 | 38 | final PlanetilerConfig params = PlanetilerConfig.defaults(); 39 | final OpenMapTilesProfile profile = new OpenMapTilesProfile(translations, PlanetilerConfig.defaults(), 40 | Stats.inMemory()); 41 | final Stats stats = Stats.inMemory(); 42 | final FeatureCollector.Factory featureCollectorFactory = new FeatureCollector.Factory(params, stats); 43 | 44 | static void assertFeatures(int zoom, List> expected, Iterable actual) { 45 | // ensure both are sorted by layer 46 | var expectedList = 47 | expected.stream().sorted(Comparator.comparing(d -> Utils.coalesce(d.get("_layer"), "").toString())).toList(); 48 | var actualList = StreamSupport.stream(actual.spliterator(), false) 49 | .sorted(Comparator.comparing(FeatureCollector.Feature::getLayer)) 50 | .toList(); 51 | assertEquals(expectedList.size(), actualList.size(), () -> "size: " + actualList); 52 | for (int i = 0; i < expectedList.size(); i++) { 53 | assertSubmap(expectedList.get(i), TestUtils.toMap(actualList.get(i), zoom)); 54 | } 55 | } 56 | 57 | static void assertDescending(int... vals) { 58 | for (int i = 1; i < vals.length; i++) { 59 | if (vals[i - 1] < vals[i]) { 60 | fail("element at " + (i - 1) + " is less than element at " + i); 61 | } 62 | } 63 | } 64 | 65 | static void assertAscending(int... vals) { 66 | for (int i = 1; i < vals.length; i++) { 67 | if (vals[i - 1] > vals[i]) { 68 | fail( 69 | Arrays.toString(vals) + 70 | System.lineSeparator() + "element at " + (i - 1) + " (" + vals[i - 1] + ") is greater than element at " + 71 | i + " (" + vals[i] + ")"); 72 | } 73 | } 74 | } 75 | 76 | VectorTile.Feature pointFeature(String layer, Map map, int group) { 77 | return new VectorTile.Feature( 78 | layer, 79 | 1, 80 | VectorTile.encodeGeometry(newPoint(0, 0)), 81 | new HashMap<>(map), 82 | group 83 | ); 84 | } 85 | 86 | FeatureCollector process(SourceFeature feature) { 87 | var collector = featureCollectorFactory.get(feature); 88 | profile.processFeature(feature, collector); 89 | return collector; 90 | } 91 | 92 | void assertCoversZoomRange(int minzoom, int maxzoom, String layer, FeatureCollector... featureCollectors) { 93 | Map[] zooms = new Map[Math.max(15, maxzoom + 1)]; 94 | for (var features : featureCollectors) { 95 | for (var feature : features) { 96 | if (feature.getLayer().equals(layer)) { 97 | for (int zoom = feature.getMinZoom(); zoom <= feature.getMaxZoom(); zoom++) { 98 | Map map = TestUtils.toMap(feature, zoom); 99 | if (zooms[zoom] != null) { 100 | fail("Multiple features at z" + zoom + ":" + System.lineSeparator() + zooms[zoom] + "\n" + map); 101 | } 102 | zooms[zoom] = map; 103 | } 104 | } 105 | } 106 | } 107 | for (int zoom = 0; zoom <= 14; zoom++) { 108 | if (zoom < minzoom || zoom > maxzoom) { 109 | if (zooms[zoom] != null) { 110 | fail("Expected nothing at z" + zoom + " but found: " + zooms[zoom]); 111 | } 112 | } else { 113 | if (zooms[zoom] == null) { 114 | fail("No feature at z" + zoom); 115 | } 116 | } 117 | } 118 | } 119 | 120 | SourceFeature pointFeature(Map props) { 121 | return SimpleFeature.create( 122 | newPoint(0, 0), 123 | new HashMap<>(props), 124 | OpenMapTilesProfile.OSM_SOURCE, 125 | null, 126 | 0 127 | ); 128 | } 129 | 130 | SourceFeature lineFeature(Map props) { 131 | return SimpleFeature.create( 132 | newLineString(0, 0, 1, 1), 133 | new HashMap<>(props), 134 | OpenMapTilesProfile.OSM_SOURCE, 135 | null, 136 | 0 137 | ); 138 | } 139 | 140 | SourceFeature lineFeatureWithLength(double length, Map props) { 141 | return SimpleFeature.create( 142 | GeoUtils.worldToLatLonCoords(newLineString(0, 0, 0, length)), 143 | new HashMap<>(props), 144 | OpenMapTilesProfile.OSM_SOURCE, 145 | null, 146 | 0 147 | ); 148 | } 149 | 150 | SourceFeature closedWayFeature(Map props) { 151 | return SimpleFeature.createFakeOsmFeature( 152 | newLineString(0, 0, 1, 0, 1, 1, 0, 1, 0, 0), 153 | new HashMap<>(props), 154 | OpenMapTilesProfile.OSM_SOURCE, 155 | null, 156 | 0, 157 | null 158 | ); 159 | } 160 | 161 | SourceFeature polygonFeatureWithArea(double area, Map props) { 162 | return SimpleFeature.create( 163 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(area))), 164 | new HashMap<>(props), 165 | OpenMapTilesProfile.OSM_SOURCE, 166 | null, 167 | 0 168 | ); 169 | } 170 | 171 | SourceFeature polygonFeature(Map props) { 172 | return polygonFeatureWithArea(1, props); 173 | } 174 | 175 | 176 | protected SimpleFeature lineFeatureWithRelation(List relationInfos, 177 | Map map) { 178 | return SimpleFeature.createFakeOsmFeature( 179 | newLineString(0, 0, 1, 1), 180 | map, 181 | OpenMapTilesProfile.OSM_SOURCE, 182 | null, 183 | 0, 184 | (relationInfos == null ? List.of() : relationInfos).stream() 185 | .map(r -> new OsmReader.RelationMember<>("", r)).toList() 186 | ); 187 | } 188 | 189 | protected void testMergesLinestrings(Map attrs, String layer, double length, int zoom) 190 | throws GeometryException { 191 | var line1 = new VectorTile.Feature( 192 | layer, 193 | 1, 194 | VectorTile.encodeGeometry(newLineString(0, 0, length / 2, 0)), 195 | new HashMap<>(attrs), 196 | 0 197 | ); 198 | var line2 = new VectorTile.Feature( 199 | layer, 200 | 1, 201 | VectorTile.encodeGeometry(newLineString(length / 2, 0, length, 0)), 202 | new HashMap<>(attrs), 203 | 0 204 | ); 205 | var connected = new VectorTile.Feature( 206 | layer, 207 | 1, 208 | VectorTile.encodeGeometry(newLineString(0, 0, length, 0)), 209 | attrs, 210 | 0 211 | ); 212 | 213 | assertEquals( 214 | List.of(connected), 215 | profile.postProcessLayerFeatures(layer, zoom, List.of(line1, line2)) 216 | ); 217 | } 218 | 219 | protected void testDoesNotMergeLinestrings(Map attrs, String layer, double length, int zoom) 220 | throws GeometryException { 221 | var line1 = new VectorTile.Feature( 222 | layer, 223 | 1, 224 | VectorTile.encodeGeometry(newLineString(0, 0, length / 2, 0)), 225 | new HashMap<>(attrs), 226 | 0 227 | ); 228 | var line2 = new VectorTile.Feature( 229 | layer, 230 | 1, 231 | VectorTile.encodeGeometry(newLineString(length / 2, 0, length, 0)), 232 | new HashMap<>(attrs), 233 | 0 234 | ); 235 | 236 | assertEquals( 237 | List.of(line1, line2), 238 | profile.postProcessLayerFeatures(layer, zoom, List.of(line1, line2)) 239 | ); 240 | } 241 | 242 | public static Map mapOf(Object... args) { 243 | assert args.length % 2 == 0; 244 | Map result = new HashMap<>(); 245 | for (int i = 0; i < args.length; i += 2) { 246 | String key = args[i].toString(); 247 | Object value = args[i + 1]; 248 | result.put(key, value == null ? "" : value); 249 | } 250 | return result; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/AerodromeLabelTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class AerodromeLabelTest extends AbstractLayerTest { 9 | 10 | @BeforeEach 11 | public void setupWikidataTranslation() { 12 | wikidataTranslations.put(123, "es", "es wd name"); 13 | } 14 | 15 | @Test 16 | void testIntlWithIata() { 17 | assertFeatures(14, List.of(Map.of( 18 | "class", "international", 19 | "ele", 100, 20 | "ele_ft", 328, 21 | "name", "osm name", 22 | "name:es", "es wd name", 23 | 24 | "_layer", "aerodrome_label", 25 | "_type", "point", 26 | "_minzoom", 8, 27 | "_maxzoom", 14, 28 | "_buffer", 64d 29 | )), process(pointFeature(Map.of( 30 | "aeroway", "aerodrome", 31 | "name", "osm name", 32 | "wikidata", "Q123", 33 | "ele", "100", 34 | "aerodrome", "international", 35 | "iata", "123", 36 | "icao", "1234" 37 | )))); 38 | } 39 | 40 | @Test 41 | void testElevationFeet() { 42 | assertFeatures(14, List.of(Map.of( 43 | "ele", 100, 44 | "ele_ft", 328 45 | )), process(pointFeature(Map.of( 46 | "aeroway", "aerodrome", 47 | "name", "osm name", 48 | "ele", "328'", 49 | "aerodrome", "international", 50 | "iata", "123", 51 | "icao", "1234" 52 | )))); 53 | } 54 | 55 | @Test 56 | void testElevationFeetInches() { 57 | assertFeatures(14, List.of(Map.of( 58 | "ele", 100, 59 | "ele_ft", 328 60 | )), process(pointFeature(Map.of( 61 | "aeroway", "aerodrome", 62 | "name", "osm name", 63 | "ele", "328' 1\"", 64 | "aerodrome", "international", 65 | "iata", "123", 66 | "icao", "1234" 67 | )))); 68 | } 69 | 70 | @Test 71 | void testInternational() { 72 | assertFeatures(14, List.of(Map.of( 73 | "class", "international", 74 | "_layer", "aerodrome_label", 75 | "_minzoom", 10 // no IATA 76 | )), process(pointFeature(Map.of( 77 | "aeroway", "aerodrome", 78 | "aerodrome_type", "international" 79 | )))); 80 | } 81 | 82 | @Test 83 | void testPublic() { 84 | assertFeatures(14, List.of(Map.of( 85 | "class", "public", 86 | "_layer", "aerodrome_label", 87 | "_minzoom", 10 88 | )), process(pointFeature(Map.of( 89 | "aeroway", "aerodrome", 90 | "aerodrome_type", "public airport" 91 | )))); 92 | assertFeatures(14, List.of(Map.of( 93 | "class", "public", 94 | "_layer", "aerodrome_label" 95 | )), process(pointFeature(Map.of( 96 | "aeroway", "aerodrome", 97 | "aerodrome_type", "civil" 98 | )))); 99 | } 100 | 101 | @Test 102 | void testMilitary() { 103 | assertFeatures(14, List.of(Map.of( 104 | "class", "military", 105 | "_layer", "aerodrome_label", 106 | "_minzoom", 10 107 | )), process(pointFeature(Map.of( 108 | "aeroway", "aerodrome", 109 | "aerodrome_type", "military airport" 110 | )))); 111 | assertFeatures(14, List.of(Map.of( 112 | "class", "military", 113 | "_layer", "aerodrome_label" 114 | )), process(pointFeature(Map.of( 115 | "aeroway", "aerodrome", 116 | "military", "airfield" 117 | )))); 118 | } 119 | 120 | @Test 121 | void testPrivate() { 122 | assertFeatures(14, List.of(Map.of( 123 | "class", "private", 124 | "_layer", "aerodrome_label", 125 | "_minzoom", 10 126 | )), process(pointFeature(Map.of( 127 | "aeroway", "aerodrome", 128 | "aerodrome_type", "private" 129 | )))); 130 | assertFeatures(14, List.of(Map.of( 131 | "class", "private", 132 | "_layer", "aerodrome_label" 133 | )), process(pointFeature(Map.of( 134 | "aeroway", "aerodrome", 135 | "aerodrome", "private" 136 | )))); 137 | } 138 | 139 | @Test 140 | void testOther() { 141 | assertFeatures(14, List.of(Map.of( 142 | "class", "other", 143 | "_layer", "aerodrome_label", 144 | "_minzoom", 10 145 | )), process(pointFeature(Map.of( 146 | "aeroway", "aerodrome" 147 | )))); 148 | } 149 | 150 | @Test 151 | void testIgnoreNonPoints() { 152 | assertFeatures(14, List.of(), process(lineFeature(Map.of( 153 | "aeroway", "aerodrome" 154 | )))); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/AerowayTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import org.junit.jupiter.api.Test; 6 | 7 | class AerowayTest extends AbstractLayerTest { 8 | 9 | @Test 10 | void aerowayGate() { 11 | assertFeatures(14, List.of(Map.of( 12 | "class", "gate", 13 | "ref", "123", 14 | 15 | "_layer", "aeroway", 16 | "_type", "point", 17 | "_minzoom", 14, 18 | "_maxzoom", 14, 19 | "_buffer", 4d 20 | )), process(pointFeature(Map.of( 21 | "aeroway", "gate", 22 | "ref", "123" 23 | )))); 24 | assertFeatures(14, List.of(), process(lineFeature(Map.of( 25 | "aeroway", "gate" 26 | )))); 27 | assertFeatures(14, List.of(), process(polygonFeature(Map.of( 28 | "aeroway", "gate" 29 | )))); 30 | } 31 | 32 | @Test 33 | void aerowayLine() { 34 | assertFeatures(14, List.of(Map.of( 35 | "class", "runway", 36 | "ref", "123", 37 | 38 | "_layer", "aeroway", 39 | "_type", "line", 40 | "_minzoom", 10, 41 | "_maxzoom", 14, 42 | "_buffer", 4d 43 | )), process(lineFeature(Map.of( 44 | "aeroway", "runway", 45 | "ref", "123" 46 | )))); 47 | assertFeatures(14, List.of(), process(pointFeature(Map.of( 48 | "aeroway", "runway" 49 | )))); 50 | } 51 | 52 | @Test 53 | void aerowayPolygon() { 54 | assertFeatures(14, List.of(Map.of( 55 | "class", "runway", 56 | "ref", "123", 57 | 58 | "_layer", "aeroway", 59 | "_type", "polygon", 60 | "_minzoom", 10, 61 | "_maxzoom", 14, 62 | "_buffer", 4d 63 | )), process(polygonFeature(Map.of( 64 | "aeroway", "runway", 65 | "ref", "123" 66 | )))); 67 | assertFeatures(14, List.of(Map.of( 68 | "class", "runway", 69 | "ref", "123", 70 | "_layer", "aeroway", 71 | "_type", "polygon" 72 | )), process(polygonFeature(Map.of( 73 | "area:aeroway", "runway", 74 | "ref", "123" 75 | )))); 76 | assertFeatures(14, List.of(Map.of( 77 | "class", "heliport", 78 | "ref", "123", 79 | "_layer", "aeroway", 80 | "_type", "polygon" 81 | )), process(polygonFeature(Map.of( 82 | "aeroway", "heliport", 83 | "ref", "123" 84 | )))); 85 | assertFeatures(14, List.of(), process(lineFeature(Map.of( 86 | "aeroway", "heliport" 87 | )))); 88 | assertFeatures(14, List.of(), process(pointFeature(Map.of( 89 | "aeroway", "heliport" 90 | )))); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/BuildingTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.rectangle; 4 | 5 | import com.onthegomap.planetiler.VectorTile; 6 | import com.onthegomap.planetiler.geo.GeoUtils; 7 | import com.onthegomap.planetiler.geo.GeometryException; 8 | import com.onthegomap.planetiler.reader.SimpleFeature; 9 | import com.onthegomap.planetiler.reader.osm.OsmElement; 10 | import com.onthegomap.planetiler.reader.osm.OsmReader; 11 | import java.util.List; 12 | import java.util.Map; 13 | import org.junit.jupiter.api.Assertions; 14 | import org.junit.jupiter.api.Test; 15 | import org.openmaptiles.OpenMapTilesProfile; 16 | 17 | class BuildingTest extends AbstractLayerTest { 18 | 19 | @Test 20 | void testBuilding() { 21 | assertFeatures(13, List.of(Map.of( 22 | "colour", "", 23 | "hide_3d", "", 24 | "_layer", "building", 25 | "_type", "polygon", 26 | "_minzoom", 13, 27 | "_maxzoom", 14, 28 | "_buffer", 4d, 29 | "_minpixelsize", 0.1d 30 | )), process(polygonFeature(Map.of( 31 | "building", "yes" 32 | )))); 33 | assertFeatures(13, List.of(Map.of( 34 | "_layer", "building", 35 | "_type", "polygon" 36 | )), process(polygonFeature(Map.of( 37 | "building:part", "yes" 38 | )))); 39 | assertFeatures(13, List.of(), process(polygonFeature(Map.of( 40 | "building", "no" 41 | )))); 42 | } 43 | 44 | @Test 45 | void testIgnoreUndergroundBuilding() { 46 | assertFeatures(14, List.of(), process(polygonFeature(Map.of( 47 | "building", "yes", 48 | "location", "underground" 49 | )))); 50 | } 51 | 52 | @Test 53 | void testAirportBuildings() { 54 | assertFeatures(13, List.of(Map.of( 55 | "_layer", "building", 56 | "_type", "polygon" 57 | )), process(polygonFeature(Map.of( 58 | "aeroway", "terminal" 59 | )))); 60 | assertFeatures(13, List.of(Map.of( 61 | "_layer", "building", 62 | "_type", "polygon" 63 | )), process(polygonFeature(Map.of( 64 | "aeroway", "hangar" 65 | )))); 66 | } 67 | 68 | @Test 69 | void testRenderHeights() { 70 | assertFeatures(13, List.of(Map.of( 71 | "render_height", "", 72 | "render_min_height", "" 73 | )), process(polygonFeature(Map.of( 74 | "building", "yes" 75 | )))); 76 | assertFeatures(14, List.of(Map.of( 77 | "render_height", 5, 78 | "render_min_height", 0 79 | )), process(polygonFeature(Map.of( 80 | "building", "yes" 81 | )))); 82 | assertFeatures(14, List.of(Map.of( 83 | "render_height", 12, 84 | "render_min_height", 3 85 | )), process(polygonFeature(Map.of( 86 | "building", "yes", 87 | "building:min_height", "3", 88 | "building:height", "12" 89 | )))); 90 | assertFeatures(14, List.of(Map.of( 91 | "render_height", 44, 92 | "render_min_height", 10 93 | )), process(polygonFeature(Map.of( 94 | "building", "yes", 95 | "building:min_level", "3", 96 | "building:levels", "12" 97 | )))); 98 | assertFeatures(14, List.of(), process(polygonFeature(Map.of( 99 | "building", "yes", 100 | "building:min_level", "1500", 101 | "building:levels", "1500" 102 | )))); 103 | } 104 | 105 | @Test 106 | void testOutlineHides3d() { 107 | var relation = new OsmElement.Relation(1); 108 | relation.setTag("type", "building"); 109 | 110 | var relationInfos = profile.preprocessOsmRelation(relation).stream() 111 | .map(i -> new OsmReader.RelationMember<>("outline", i)).toList(); 112 | 113 | assertFeatures(14, List.of(Map.of( 114 | "_layer", "building", 115 | "hide_3d", true 116 | )), process(SimpleFeature.createFakeOsmFeature( 117 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 118 | Map.of( 119 | "building", "yes" 120 | ), 121 | OpenMapTilesProfile.OSM_SOURCE, 122 | null, 123 | 0, 124 | relationInfos 125 | ))); 126 | } 127 | 128 | @Test 129 | void testMergePolygonsZ13() throws GeometryException { 130 | var poly1 = new VectorTile.Feature( 131 | Building.LAYER_NAME, 132 | 1, 133 | VectorTile.encodeGeometry(rectangle(10, 20)), 134 | Map.of(), 135 | 0 136 | ); 137 | var poly2 = new VectorTile.Feature( 138 | Building.LAYER_NAME, 139 | 1, 140 | VectorTile.encodeGeometry(rectangle(20, 10, 22, 20)), 141 | Map.of(), 142 | 0 143 | ); 144 | 145 | Assertions.assertEquals( 146 | 1, // merged into 1 multipolygon 147 | profile.postProcessLayerFeatures(Building.LAYER_NAME, 14, List.of(poly1, poly2)).size() 148 | ); 149 | 150 | Assertions.assertEquals( 151 | 2, // merged into 1 multipolygon 152 | profile.postProcessLayerFeatures(Building.LAYER_NAME, 14, List.of(poly1, poly2)).get(0).geometry().decode() 153 | .getNumGeometries() 154 | ); 155 | Assertions.assertEquals( 156 | 1, 157 | profile.postProcessLayerFeatures(Building.LAYER_NAME, 13, List.of(poly1, poly2)).size() 158 | ); 159 | Assertions.assertEquals( 160 | 1, 161 | profile.postProcessLayerFeatures(Building.LAYER_NAME, 13, List.of(poly1, poly2)).get(0).geometry().decode() 162 | .getNumGeometries() 163 | ); 164 | } 165 | 166 | @Test 167 | void testColor() { 168 | assertFeatures(14, List.of(Map.of( 169 | "colour", "#ff0000" 170 | )), process(polygonFeature(Map.of( 171 | "building", "yes", 172 | "building:colour", "#ff0000", 173 | "building:material", "brick" 174 | )))); 175 | assertFeatures(14, List.of(Map.of( 176 | "colour", "#bd8161" 177 | )), process(polygonFeature(Map.of( 178 | "building", "yes", 179 | "building:building", "yes", 180 | "building:material", "brick" 181 | )))); 182 | assertFeatures(13, List.of(Map.of( 183 | "colour", "" 184 | )), process(polygonFeature(Map.of( 185 | "building", "yes", 186 | "building:building", "yes", 187 | "building:colour", "#ff0000" 188 | )))); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/HousenumberTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import com.onthegomap.planetiler.geo.GeometryException; 6 | import java.util.List; 7 | import java.util.Map; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.params.ParameterizedTest; 11 | import org.junit.jupiter.params.provider.CsvSource; 12 | 13 | class HousenumberTest extends AbstractLayerTest { 14 | 15 | @Test 16 | void testHousenumber() { 17 | assertFeatures(14, List.of(Map.of( 18 | "_layer", "housenumber", 19 | "_type", "point", 20 | "_minzoom", 14, 21 | "_maxzoom", 14, 22 | "_buffer", 8d 23 | )), process(pointFeature(Map.of( 24 | "addr:housenumber", "10" 25 | )))); 26 | assertFeatures(14, List.of(Map.of( 27 | "_layer", "housenumber", 28 | "_type", "point", 29 | "_minzoom", 14, 30 | "_maxzoom", 14, 31 | "_buffer", 8d 32 | )), process(polygonFeature(Map.of( 33 | "addr:housenumber", "10" 34 | )))); 35 | } 36 | 37 | @ParameterizedTest 38 | @CsvSource({ 39 | "1, 1", 40 | "1;1a;2;2/b;20;3, 1–3", 41 | "1;1a;2;2/b;20;3;, 1–3", 42 | "1;2;20;3, 1–20", 43 | "1;2;20;3;, 1–20", 44 | ";, ;", 45 | ";;, ;;", 46 | "2712;935803935803, 2712–935803935803", 47 | }) 48 | void testDisplayHousenumber(String outlier, String expected) { 49 | assertEquals(expected, Housenumber.displayHousenumber(outlier)); 50 | } 51 | 52 | @Test 53 | void testTempAttrs() { 54 | assertFeatures(14, List.of(Map.of( 55 | "_has_name", Boolean.TRUE, 56 | "_partition", "streetX765/6" 57 | )), process(polygonFeature(Map.of( 58 | "addr:housenumber", "765/6", 59 | "addr:block_number", "X", 60 | "addr:street", "street", 61 | "name", "name" 62 | )))); 63 | } 64 | 65 | @Test 66 | void testNonduplicateHousenumber() throws GeometryException { 67 | var layerName = Housenumber.LAYER_NAME; 68 | var hn1 = pointFeature( 69 | layerName, 70 | Map.of( 71 | "housenumber", "764/2", 72 | "_partition", "764/2" 73 | ), 74 | 1 75 | ); 76 | var hn2 = pointFeature( 77 | layerName, 78 | Map.of( 79 | "housenumber", "765/6", 80 | "_partition", "765/6" 81 | ), 82 | 1 83 | ); 84 | 85 | Assertions.assertEquals( 86 | 2, 87 | profile.postProcessLayerFeatures(layerName, 14, List.of(hn1, hn2)).size() 88 | ); 89 | } 90 | 91 | @Test 92 | void testNonduplicateStreet() throws GeometryException { 93 | var layerName = Housenumber.LAYER_NAME; 94 | var housenumber = "765/6"; 95 | var hn1 = pointFeature( 96 | layerName, 97 | Map.of( 98 | "housenumber", housenumber, 99 | "_partition", "street 1" + housenumber 100 | ), 101 | 1 102 | ); 103 | var hn2 = pointFeature( 104 | layerName, 105 | Map.of( 106 | "housenumber", housenumber, 107 | "_partition", "street 2" + housenumber 108 | ), 109 | 1 110 | ); 111 | 112 | var result = profile.postProcessLayerFeatures(layerName, 14, List.of(hn1, hn2)); 113 | 114 | Assertions.assertEquals( 115 | 1, // same housenumber => two points merged into one multipoint 116 | result.size() 117 | ); 118 | Assertions.assertEquals( 119 | 5, // two point in multipoint => 5 commands 120 | result.getFirst().geometry().commands().length); 121 | } 122 | 123 | @Test 124 | void testDuplicateHousenumber() throws GeometryException { 125 | var layerName = Housenumber.LAYER_NAME; 126 | var housenumber = "765/6"; 127 | var hn1 = pointFeature( 128 | layerName, 129 | Map.of( 130 | "housenumber", housenumber + " (no name)", 131 | "_has_name", false, 132 | "_partition", housenumber 133 | ), 134 | 1 135 | ); 136 | var hn2 = pointFeature( 137 | layerName, 138 | Map.of( 139 | "housenumber", housenumber + " (with name)", 140 | "_has_name", true, 141 | "_partition", housenumber 142 | ), 143 | 1 144 | ); 145 | 146 | var result = profile.postProcessLayerFeatures(layerName, 14, List.of(hn1, hn2)); 147 | 148 | Assertions.assertEquals(List.of( 149 | pointFeature( 150 | layerName, 151 | Map.of("housenumber", "765/6 (no name)"), 152 | 1 153 | ) 154 | ), result); 155 | Assertions.assertEquals( 156 | 3, // only one point in multipoint => 3 commands 157 | result.getFirst().geometry().commands().length); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/LandcoverTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.rectangle; 4 | 5 | import com.onthegomap.planetiler.VectorTile; 6 | import com.onthegomap.planetiler.geo.GeoUtils; 7 | import com.onthegomap.planetiler.geo.GeometryException; 8 | import com.onthegomap.planetiler.reader.SimpleFeature; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import org.junit.jupiter.api.Assertions; 13 | import org.junit.jupiter.api.Test; 14 | import org.openmaptiles.OpenMapTilesProfile; 15 | 16 | class LandcoverTest extends AbstractLayerTest { 17 | 18 | @Test 19 | void testNaturalEarthGlaciers() { 20 | var glacier1 = process(SimpleFeature.create( 21 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 22 | Map.of(), 23 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 24 | "ne_110m_glaciated_areas", 25 | 0 26 | )); 27 | var glacier2 = process(SimpleFeature.create( 28 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 29 | Map.of(), 30 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 31 | "ne_50m_glaciated_areas", 32 | 0 33 | )); 34 | var glacier3 = process(SimpleFeature.create( 35 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 36 | Map.of(), 37 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 38 | "ne_10m_glaciated_areas", 39 | 0 40 | )); 41 | assertFeatures(0, List.of(Map.of( 42 | "_layer", "landcover", 43 | "subclass", "glacier", 44 | "class", "ice", 45 | "_buffer", 4d 46 | )), glacier1); 47 | assertFeatures(0, List.of(Map.of( 48 | "_layer", "landcover", 49 | "subclass", "glacier", 50 | "class", "ice", 51 | "_buffer", 4d 52 | )), glacier2); 53 | assertFeatures(0, List.of(Map.of( 54 | "_layer", "landcover", 55 | "subclass", "glacier", 56 | "class", "ice", 57 | "_buffer", 4d 58 | )), glacier3); 59 | assertCoversZoomRange(0, 6, "landcover", 60 | glacier1, 61 | glacier2, 62 | glacier3 63 | ); 64 | } 65 | 66 | @Test 67 | void testNaturalEarthAntarcticIceShelves() { 68 | var ice1 = process(SimpleFeature.create( 69 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 70 | Map.of(), 71 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 72 | "ne_50m_antarctic_ice_shelves_polys", 73 | 0 74 | )); 75 | var ice2 = process(SimpleFeature.create( 76 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 77 | Map.of(), 78 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 79 | "ne_10m_antarctic_ice_shelves_polys", 80 | 0 81 | )); 82 | assertFeatures(0, List.of(Map.of( 83 | "_layer", "landcover", 84 | "subclass", "ice_shelf", 85 | "class", "ice", 86 | "_buffer", 4d 87 | )), ice1); 88 | assertFeatures(0, List.of(Map.of( 89 | "_layer", "landcover", 90 | "subclass", "ice_shelf", 91 | "class", "ice", 92 | "_buffer", 4d 93 | )), ice2); 94 | assertCoversZoomRange(2, 6, "landcover", 95 | ice1, 96 | ice2 97 | ); 98 | } 99 | 100 | @Test 101 | void testOsmLandcover() { 102 | assertFeatures(13, List.of(Map.of( 103 | "_layer", "landcover", 104 | "subclass", "wood", 105 | "class", "wood", 106 | "_minpixelsize", 8d, 107 | "_numpointsattr", "_numpoints", 108 | "_minzoom", 7, 109 | "_maxzoom", 14 110 | )), process(polygonFeature(Map.of( 111 | "natural", "wood" 112 | )))); 113 | assertFeatures(12, List.of(Map.of( 114 | "_layer", "landcover", 115 | "subclass", "forest", 116 | "class", "wood", 117 | "_minpixelsize", 8d, 118 | "_minzoom", 7, 119 | "_maxzoom", 14 120 | )), process(polygonFeature(Map.of( 121 | "landuse", "forest" 122 | )))); 123 | assertFeatures(10, List.of(Map.of( 124 | "_layer", "landcover", 125 | "subclass", "dune", 126 | "class", "sand", 127 | "_minpixelsize", 4d, 128 | "_minzoom", 7, 129 | "_maxzoom", 14 130 | )), process(polygonFeature(Map.of( 131 | "natural", "dune" 132 | )))); 133 | assertFeatures(10, List.of(), process(polygonFeature(Map.of( 134 | "landuse", "park" 135 | )))); 136 | } 137 | 138 | @Test 139 | void testMergeForestsBuNumPointsZ9to13() throws GeometryException { 140 | Map map = Map.of("subclass", "wood"); 141 | 142 | assertMerges(List.of(map, map, map, map, map, map), List.of( 143 | // don't merge any 144 | feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "wood")), 145 | feature(rectangle(10, 20), Map.of("_numpoints", 49, "subclass", "wood")), 146 | feature(rectangle(12, 18), Map.of("_numpoints", 50, "subclass", "wood")), 147 | feature(rectangle(12, 18), Map.of("_numpoints", 299, "subclass", "wood")), 148 | feature(rectangle(12, 18), Map.of("_numpoints", 300, "subclass", "wood")), 149 | feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "wood")) 150 | ), 14); 151 | assertMerges(List.of(map, map, map), List.of( 152 | // < 300 - merge 153 | feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "wood")), 154 | feature(rectangle(10, 20), Map.of("_numpoints", 49, "subclass", "wood")), 155 | feature(rectangle(12, 18), Map.of("_numpoints", 50, "subclass", "wood")), 156 | feature(rectangle(12, 18), Map.of("_numpoints", 299, "subclass", "wood")), 157 | // >= 300 - don't merge 158 | feature(rectangle(12, 18), Map.of("_numpoints", 300, "subclass", "wood")), 159 | feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "wood")) 160 | ), 13); 161 | assertMerges(List.of(map, map), List.of( 162 | // < 300 - merge 163 | feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "wood")), 164 | feature(rectangle(10, 20), Map.of("_numpoints", 49, "subclass", "wood")), 165 | feature(rectangle(12, 18), Map.of("_numpoints", 50, "subclass", "wood")), 166 | feature(rectangle(12, 18), Map.of("_numpoints", 299, "subclass", "wood")), 167 | // >= 300 - merge 168 | feature(rectangle(12, 18), Map.of("_numpoints", 300, "subclass", "wood")), 169 | feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "wood")) 170 | ), 9); 171 | } 172 | 173 | @Test 174 | void testMergeNonForestsBelowZ8() throws GeometryException { 175 | Map map = Map.of("subclass", "dune"); 176 | 177 | assertMerges(List.of(map, map), List.of( 178 | feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "dune")), 179 | feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "dune")) 180 | ), 8); 181 | assertMerges(List.of(map), List.of( 182 | feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "dune")), 183 | feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "dune")) 184 | ), 7); 185 | assertMerges(List.of(map, map), List.of( 186 | feature(rectangle(10, 20), Map.of("_numpoints", 48, "subclass", "dune")), 187 | feature(rectangle(12, 18), Map.of("_numpoints", 301, "subclass", "dune")) 188 | ), 6); 189 | } 190 | 191 | 192 | private VectorTile.Feature feature(org.locationtech.jts.geom.Polygon geom, Map m) { 193 | return new VectorTile.Feature( 194 | "landcover", 195 | 1, 196 | VectorTile.encodeGeometry(geom), 197 | new HashMap<>(m), 198 | 0 199 | ); 200 | } 201 | 202 | private void assertMerges(List> expected, List in, int zoom) 203 | throws GeometryException { 204 | Assertions.assertEquals(expected, 205 | profile.postProcessLayerFeatures("landcover", zoom, in).stream().map( 206 | VectorTile.Feature::tags) 207 | .toList()); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/LanduseTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.rectangle; 4 | 5 | import com.onthegomap.planetiler.VectorTile; 6 | import com.onthegomap.planetiler.geo.GeoUtils; 7 | import com.onthegomap.planetiler.geo.GeometryException; 8 | import com.onthegomap.planetiler.reader.SimpleFeature; 9 | import java.util.List; 10 | import java.util.Map; 11 | import org.junit.jupiter.api.Assertions; 12 | import org.junit.jupiter.api.Test; 13 | import org.openmaptiles.OpenMapTilesProfile; 14 | 15 | class LanduseTest extends AbstractLayerTest { 16 | 17 | @Test 18 | void testNaturalEarthUrbanAreas() { 19 | assertFeatures(0, List.of(Map.of( 20 | "_layer", "landuse", 21 | "class", "residential", 22 | "_buffer", 4d, 23 | "_minzoom", 4 24 | )), process(SimpleFeature.create( 25 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 26 | Map.of("scalerank", 1.9), 27 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 28 | "ne_50m_urban_areas", 29 | 0 30 | ))); 31 | assertFeatures(0, List.of(Map.of( 32 | "_layer", "landuse", 33 | "class", "residential", 34 | "_buffer", 4d, 35 | "_minzoom", 5 36 | )), process(SimpleFeature.create( 37 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 38 | Map.of("scalerank", 2.1), 39 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 40 | "ne_50m_urban_areas", 41 | 0 42 | ))); 43 | } 44 | 45 | @Test 46 | void testOsmLanduse() { 47 | assertFeatures(13, List.of( 48 | Map.of("_layer", "poi"), 49 | Map.of( 50 | "_layer", "landuse", 51 | "class", "railway", 52 | "_minpixelsize", 4d, 53 | "_minzoom", 9, 54 | "_maxzoom", 14 55 | )), process(polygonFeature(Map.of( 56 | "landuse", "railway", 57 | "amenity", "school" 58 | )))); 59 | assertFeatures(13, List.of(Map.of("_layer", "poi"), Map.of( 60 | "_layer", "landuse", 61 | "class", "school", 62 | "_minpixelsize", 4d, 63 | "_minzoom", 9, 64 | "_maxzoom", 14 65 | )), process(polygonFeature(Map.of( 66 | "amenity", "school" 67 | )))); 68 | } 69 | 70 | @Test 71 | void testGraveYardBecomesCemetery() { 72 | assertFeatures(14, List.of( 73 | Map.of("_layer", "poi"), 74 | Map.of( 75 | "_layer", "landuse", 76 | "class", "cemetery" 77 | )), process(polygonFeature(Map.of( 78 | "amenity", "grave_yard" 79 | )))); 80 | } 81 | 82 | @Test 83 | void testOsmLanduseLowerZoom() { 84 | assertFeatures(6, List.of(Map.of( 85 | "_layer", "landuse", 86 | "class", "suburb", 87 | "_minzoom", 6, 88 | "_maxzoom", 14, 89 | "_minpixelsize", 1d 90 | )), process(polygonFeature(Map.of( 91 | "place", "suburb" 92 | )))); 93 | assertFeatures(7, List.of(Map.of( 94 | "_layer", "landuse", 95 | "class", "residential", 96 | "_minzoom", 6, 97 | "_maxzoom", 14, 98 | "_minpixelsize", 0.1d 99 | )), process(polygonFeature(Map.of( 100 | "landuse", "residential" 101 | )))); 102 | } 103 | 104 | @Test 105 | void testMergePolygonsZ12() throws GeometryException { 106 | var poly1 = new VectorTile.Feature( 107 | Landuse.LAYER_NAME, 108 | 1, 109 | VectorTile.encodeGeometry(rectangle(10, 20)), 110 | Map.of("class", "residential"), 111 | 0 112 | ); 113 | var poly2 = new VectorTile.Feature( 114 | Landuse.LAYER_NAME, 115 | 1, 116 | VectorTile.encodeGeometry(rectangle(20, 10, 22, 20)), 117 | Map.of("class", "residential"), 118 | 0 119 | ); 120 | var poly3 = new VectorTile.Feature( 121 | Landuse.LAYER_NAME, 122 | 1, 123 | VectorTile.encodeGeometry(rectangle(10, 20, 20, 22)), 124 | Map.of("class", "suburb"), 125 | 0 126 | ); 127 | 128 | Assertions.assertEquals( 129 | List.of(1, 2), 130 | profile.postProcessLayerFeatures(Landuse.LAYER_NAME, 13, List.of(poly1, poly2, poly3)).stream() 131 | .map(d -> { 132 | try { 133 | return d.geometry().decode().getNumGeometries(); 134 | } catch (GeometryException e) { 135 | throw new AssertionError(e); 136 | } 137 | }).toList() 138 | ); 139 | Assertions.assertEquals( 140 | List.of(1, 1), 141 | profile.postProcessLayerFeatures(Landuse.LAYER_NAME, 12, List.of(poly1, poly2, poly3)).stream() 142 | .map(d -> { 143 | try { 144 | return d.geometry().decode().getNumGeometries(); 145 | } catch (GeometryException e) { 146 | throw new AssertionError(e); 147 | } 148 | }).toList() 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/MountainPeakTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.newPoint; 4 | import static com.onthegomap.planetiler.TestUtils.rectangle; 5 | 6 | import com.google.common.collect.Lists; 7 | import com.onthegomap.planetiler.VectorTile; 8 | import com.onthegomap.planetiler.geo.GeometryException; 9 | import com.onthegomap.planetiler.reader.SimpleFeature; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import org.junit.jupiter.api.Assertions; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | import org.openmaptiles.OpenMapTilesProfile; 17 | 18 | class MountainPeakTest extends AbstractLayerTest { 19 | 20 | @BeforeEach 21 | public void setupWikidataTranslation() { 22 | wikidataTranslations.put(123, "es", "es wd name"); 23 | } 24 | 25 | @Test 26 | void testHappyPath() { 27 | var peak = process(pointFeature(Map.of( 28 | "natural", "peak", 29 | "name", "test", 30 | "ele", "100", 31 | "wikidata", "Q123" 32 | ))); 33 | assertFeatures(14, List.of(Map.of( 34 | "class", "peak", 35 | "ele", 100, 36 | "ele_ft", 328, 37 | "customary_ft", "", 38 | 39 | "_layer", "mountain_peak", 40 | "_type", "point", 41 | "_minzoom", 7, 42 | "_maxzoom", 14, 43 | "_buffer", 100d 44 | )), peak); 45 | assertFeatures(14, List.of(Map.of( 46 | "name:latin", "test", 47 | "name", "test", 48 | "name:es", "es wd name" 49 | )), peak); 50 | } 51 | 52 | @Test 53 | void testLabelGrid() { 54 | var peak = process(pointFeature(Map.of( 55 | "natural", "peak", 56 | "ele", "100" 57 | ))); 58 | assertFeatures(14, List.of(Map.of( 59 | "_labelgrid_limit", 0 60 | )), peak); 61 | assertFeatures(13, List.of(Map.of( 62 | "_labelgrid_limit", 5, 63 | "_labelgrid_size", 100d 64 | )), peak); 65 | } 66 | 67 | @Test 68 | void testVolcano() { 69 | assertFeatures(14, List.of(Map.of( 70 | "class", "volcano" 71 | )), process(pointFeature(Map.of( 72 | "natural", "volcano", 73 | "ele", "100" 74 | )))); 75 | } 76 | 77 | @Test 78 | void testElevationFeet() { 79 | assertFeatures(14, List.of(Map.of( 80 | "class", "volcano", 81 | "ele", 30, 82 | "ele_ft", 100 83 | )), process(pointFeature(Map.of( 84 | "natural", "volcano", 85 | "ele", "100'" 86 | )))); 87 | } 88 | 89 | @Test 90 | void testElevationFeetInches() { 91 | assertFeatures(14, List.of(Map.of( 92 | "class", "volcano", 93 | "ele", 31, 94 | "ele_ft", 101 95 | )), process(pointFeature(Map.of( 96 | "natural", "volcano", 97 | "ele", "100' 11\"" 98 | )))); 99 | } 100 | 101 | @Test 102 | void testSaddle() { 103 | assertFeatures(14, List.of(Map.of( 104 | "class", "saddle" 105 | )), process(pointFeature(Map.of( 106 | "natural", "saddle", 107 | "ele", "100" 108 | )))); 109 | } 110 | 111 | 112 | @Test 113 | void testNoElevation() { 114 | assertFeatures(14, List.of(), process(pointFeature(Map.of( 115 | "natural", "volcano" 116 | )))); 117 | } 118 | 119 | @Test 120 | void testBogusElevation() { 121 | assertFeatures(14, List.of(), process(pointFeature(Map.of( 122 | "natural", "volcano", 123 | "ele", "11000" 124 | )))); 125 | } 126 | 127 | @Test 128 | void testIgnorePeakLines() { 129 | assertFeatures(14, List.of(), process(lineFeature(Map.of( 130 | "natural", "peak", 131 | "name", "name", 132 | "ele", "100" 133 | )))); 134 | } 135 | 136 | @Test 137 | void testMountainLinestring() { 138 | assertFeatures(14, List.of(Map.of( 139 | "class", "ridge", 140 | "name", "Ridge", 141 | 142 | "_layer", "mountain_peak", 143 | "_type", "line", 144 | "_minzoom", 13, 145 | "_maxzoom", 14, 146 | "_buffer", 100d 147 | )), process(lineFeature(Map.of( 148 | "natural", "ridge", 149 | "name", "Ridge" 150 | )))); 151 | } 152 | 153 | @Test 154 | void testCustomaryFt() { 155 | process(SimpleFeature.create( 156 | rectangle(0, 0.1), 157 | Map.of("iso_a2", "US"), 158 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 159 | "ne_10m_admin_0_countries", 160 | 0 161 | )); 162 | 163 | // inside US - customary_ft=1 164 | assertFeatures(14, List.of(Map.of( 165 | "class", "volcano", 166 | "customary_ft", 1, 167 | "ele", 100, 168 | "ele_ft", 328 169 | )), process(SimpleFeature.create( 170 | newPoint(0, 0), 171 | new HashMap<>(Map.of( 172 | "natural", "volcano", 173 | "ele", "100" 174 | )), 175 | OpenMapTilesProfile.OSM_SOURCE, 176 | null, 177 | 0 178 | ))); 179 | 180 | // outside US - customary_ft omitted 181 | assertFeatures(14, List.of(Map.of( 182 | "class", "volcano", 183 | "customary_ft", "", 184 | "ele", 100, 185 | "ele_ft", 328 186 | )), process(SimpleFeature.create( 187 | newPoint(1, 1), 188 | new HashMap<>(Map.of( 189 | "natural", "volcano", 190 | "ele", "100" 191 | )), 192 | OpenMapTilesProfile.OSM_SOURCE, 193 | null, 194 | 0 195 | ))); 196 | } 197 | 198 | private int getSortKey(Map tags) { 199 | return process(pointFeature(Map.of( 200 | "natural", "peak", 201 | "ele", "100" 202 | ))).iterator().next().getSortKey(); 203 | } 204 | 205 | @Test 206 | void testSortKey() { 207 | assertAscending( 208 | getSortKey(Map.of( 209 | "natural", "peak", 210 | "name", "name", 211 | "wikipedia", "wikilink", 212 | "ele", "100" 213 | )), 214 | getSortKey(Map.of( 215 | "natural", "peak", 216 | "name", "name", 217 | "ele", "100" 218 | )), 219 | getSortKey(Map.of( 220 | "natural", "peak", 221 | "ele", "100" 222 | )) 223 | ); 224 | } 225 | 226 | @Test 227 | void testMountainPeakPostProcessing() throws GeometryException { 228 | Assertions.assertEquals(List.of(), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of())); 229 | 230 | Assertions.assertEquals(List.of(pointFeature( 231 | MountainPeak.LAYER_NAME, 232 | Map.of("rank", 1), 233 | 1 234 | )), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of(pointFeature( 235 | MountainPeak.LAYER_NAME, 236 | Map.of(), 237 | 1 238 | )))); 239 | 240 | Assertions.assertEquals(List.of( 241 | pointFeature( 242 | MountainPeak.LAYER_NAME, 243 | Map.of("rank", 1, "name", "a"), 244 | 1 245 | ), pointFeature( 246 | MountainPeak.LAYER_NAME, 247 | Map.of("rank", 2, "name", "b"), 248 | 1 249 | ), pointFeature( 250 | MountainPeak.LAYER_NAME, 251 | Map.of("rank", 1, "name", "c"), 252 | 2 253 | ) 254 | ), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, List.of( 255 | pointFeature( 256 | MountainPeak.LAYER_NAME, 257 | Map.of("name", "a"), 258 | 1 259 | ), 260 | pointFeature( 261 | MountainPeak.LAYER_NAME, 262 | Map.of("name", "b"), 263 | 1 264 | ), 265 | pointFeature( 266 | MountainPeak.LAYER_NAME, 267 | Map.of("name", "c"), 268 | 2 269 | ) 270 | ))); 271 | } 272 | 273 | @Test 274 | void testMountainPeakPostProcessingLimitsFeaturesOutsideZoom() throws GeometryException { 275 | Assertions.assertEquals(Lists.newArrayList( 276 | new VectorTile.Feature( 277 | MountainPeak.LAYER_NAME, 278 | 1, 279 | VectorTile.encodeGeometry(newPoint(-64, -64)), 280 | Map.of("rank", 1), 281 | 1 282 | ), 283 | null, 284 | new VectorTile.Feature( 285 | MountainPeak.LAYER_NAME, 286 | 3, 287 | VectorTile.encodeGeometry(newPoint(256 + 64, 256 + 64)), 288 | Map.of("rank", 1), 289 | 2 290 | ), 291 | null 292 | ), profile.postProcessLayerFeatures(MountainPeak.LAYER_NAME, 13, Lists.newArrayList( 293 | new VectorTile.Feature( 294 | MountainPeak.LAYER_NAME, 295 | 1, 296 | VectorTile.encodeGeometry(newPoint(-64, -64)), 297 | new HashMap<>(), 298 | 1 299 | ), 300 | new VectorTile.Feature( 301 | MountainPeak.LAYER_NAME, 302 | 2, 303 | VectorTile.encodeGeometry(newPoint(-65, -65)), 304 | new HashMap<>(), 305 | 1 306 | ), 307 | new VectorTile.Feature( 308 | MountainPeak.LAYER_NAME, 309 | 3, 310 | VectorTile.encodeGeometry(newPoint(256 + 64, 256 + 64)), 311 | new HashMap<>(), 312 | 2 313 | ), 314 | new VectorTile.Feature( 315 | MountainPeak.LAYER_NAME, 316 | 4, 317 | VectorTile.encodeGeometry(newPoint(256 + 65, 256 + 65)), 318 | new HashMap<>(), 319 | 2 320 | ) 321 | ))); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/ParkTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import com.onthegomap.planetiler.geo.GeoUtils; 4 | import java.util.List; 5 | import java.util.Map; 6 | import org.junit.jupiter.api.Test; 7 | 8 | class ParkTest extends AbstractLayerTest { 9 | 10 | @Test 11 | void testNationalPark() { 12 | assertFeatures(13, List.of(Map.of( 13 | "_layer", "park", 14 | "_type", "polygon", 15 | "class", "national_park", 16 | "name", "Grand Canyon National Park", 17 | "_minpixelsize", 2d, 18 | "_minzoom", 4, 19 | "_maxzoom", 14 20 | ), Map.of( 21 | "_layer", "park", 22 | "_type", "point", 23 | "class", "national_park", 24 | "name", "Grand Canyon National Park", 25 | "name_int", "Grand Canyon National Park", 26 | "name:latin", "Grand Canyon National Park", 27 | // "name:es", "es name", // don't include all translations 28 | "_minzoom", 5, 29 | "_maxzoom", 14 30 | )), process(polygonFeature(Map.of( 31 | "boundary", "national_park", 32 | "name", "Grand Canyon National Park", 33 | "name:es", "es name", 34 | "protection_title", "National Park", 35 | "wikipedia", "en:Grand Canyon National Park" 36 | )))); 37 | 38 | // needs a name 39 | assertFeatures(13, List.of(Map.of( 40 | "_layer", "park", 41 | "_type", "polygon" 42 | )), process(polygonFeature(Map.of( 43 | "boundary", "national_park", 44 | "protection_title", "National Park" 45 | )))); 46 | } 47 | 48 | @Test 49 | void testSmallerPark() { 50 | double z11area = Math.pow((GeoUtils.metersToPixelAtEquator(0, Math.sqrt(70_000)) / 256d), 2) * Math.pow(2, 20 - 11); 51 | assertFeatures(13, List.of(Map.of( 52 | "_layer", "park", 53 | "_type", "polygon", 54 | "class", "protected_area", 55 | "name", "Small park", 56 | "_minpixelsize", 2d, 57 | "_minzoom", 4, 58 | "_maxzoom", 14 59 | ), Map.of( 60 | "_layer", "park", 61 | "_type", "point", 62 | "class", "protected_area", 63 | "name", "Small park", 64 | "name_int", "Small park", 65 | "_minzoom", 11, 66 | "_maxzoom", 14 67 | )), process(polygonFeatureWithArea(z11area, Map.of( 68 | "boundary", "protected_area", 69 | "name", "Small park", 70 | "wikipedia", "en:Small park" 71 | )))); 72 | assertFeatures(13, List.of(Map.of( 73 | "_layer", "park", 74 | "_type", "polygon" 75 | ), Map.of( 76 | "_layer", "park", 77 | "_type", "point", 78 | "_minzoom", 5, 79 | "_maxzoom", 14 80 | )), process(polygonFeatureWithArea(1, Map.of( 81 | "boundary", "protected_area", 82 | "name", "Small park", 83 | "wikidata", "Q123" 84 | )))); 85 | } 86 | 87 | @Test 88 | void testSortKeys() { 89 | assertAscending( 90 | getLabelSortKey(1, Map.of( 91 | "boundary", "national_park", 92 | "name", "a", 93 | "wikipedia", "en:park" 94 | )), 95 | getLabelSortKey(1e-10, Map.of( 96 | "boundary", "national_park", 97 | "name", "a", 98 | "wikipedia", "en:Park" 99 | )), 100 | getLabelSortKey(1, Map.of( 101 | "boundary", "national_park", 102 | "name", "a" 103 | )), 104 | getLabelSortKey(1e-10, Map.of( 105 | "boundary", "national_park", 106 | "name", "a" 107 | )), 108 | 109 | getLabelSortKey(1, Map.of( 110 | "boundary", "protected_area", 111 | "name", "a", 112 | "wikipedia", "en:park" 113 | )), 114 | getLabelSortKey(1e-10, Map.of( 115 | "boundary", "protected_area", 116 | "name", "a", 117 | "wikipedia", "en:Park" 118 | )), 119 | getLabelSortKey(1, Map.of( 120 | "boundary", "protected_area", 121 | "name", "a" 122 | )), 123 | getLabelSortKey(1e-10, Map.of( 124 | "boundary", "protected_area", 125 | "name", "a" 126 | )) 127 | ); 128 | } 129 | 130 | private int getLabelSortKey(double area, Map tags) { 131 | var iter = process(polygonFeatureWithArea(area, tags)).iterator(); 132 | iter.next(); 133 | return iter.next().getSortKey(); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/WaterNameTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.newLineString; 4 | import static com.onthegomap.planetiler.TestUtils.rectangle; 5 | 6 | import com.onthegomap.planetiler.TestUtils; 7 | import com.onthegomap.planetiler.geo.GeoUtils; 8 | import com.onthegomap.planetiler.reader.SimpleFeature; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import org.junit.jupiter.api.Test; 13 | import org.locationtech.jts.geom.Geometry; 14 | import org.openmaptiles.OpenMapTilesProfile; 15 | 16 | class WaterNameTest extends AbstractLayerTest { 17 | 18 | @Test 19 | void testWaterNamePoint() { 20 | assertFeatures(11, List.of(Map.of( 21 | "_layer", "water" 22 | ), Map.of( 23 | "class", "lake", 24 | "name", "waterway", 25 | "name:es", "waterway es", 26 | "intermittent", 1, 27 | 28 | "_layer", "water_name", 29 | "_type", "point", 30 | "_minzoom", 3, 31 | "_maxzoom", 14 32 | )), process(polygonFeatureWithArea(1, Map.of( 33 | "name", "waterway", 34 | "name:es", "waterway es", 35 | "natural", "water", 36 | "water", "pond", 37 | "intermittent", "1" 38 | )))); 39 | } 40 | 41 | @Test 42 | void testWaterNameLakeline() { 43 | assertFeatures(11, List.of(), process(SimpleFeature.create( 44 | newLineString(0, 0, 1, 1), 45 | new HashMap<>(Map.of( 46 | "OSM_ID", -10 47 | )), 48 | OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, 49 | null, 50 | 0 51 | ))); 52 | assertFeatures(10, List.of(Map.of( 53 | "_layer", "water" 54 | ), Map.of( 55 | "name", "waterway", 56 | "name:es", "waterway es", 57 | 58 | "_layer", "water_name", 59 | "_type", "line", 60 | "_geom", new TestUtils.NormGeometry(GeoUtils.latLonToWorldCoords(newLineString(0, 0, 1, 1))), 61 | "_minzoom", 3, 62 | "_maxzoom", 14, 63 | "_minpixelsize", "waterway".length() * 6d 64 | )), process(SimpleFeature.create( 65 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1E-7))), 66 | new HashMap<>(Map.of( 67 | "name", "waterway", 68 | "name:es", "waterway es", 69 | "natural", "water", 70 | "water", "pond" 71 | )), 72 | OpenMapTilesProfile.OSM_SOURCE, 73 | null, 74 | 10 75 | ))); 76 | } 77 | 78 | @Test 79 | void testWaterNameMultipleLakelines() { 80 | assertFeatures(11, List.of(), process(SimpleFeature.create( 81 | newLineString(0, 0, 1, 1), 82 | new HashMap<>(Map.of( 83 | "OSM_ID", -10 84 | )), 85 | OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, 86 | null, 87 | 0 88 | ))); 89 | assertFeatures(11, List.of(), process(SimpleFeature.create( 90 | newLineString(2, 2, 3, 3), 91 | new HashMap<>(Map.of( 92 | "OSM_ID", -10 93 | )), 94 | OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, 95 | null, 96 | 0 97 | ))); 98 | assertFeatures(10, List.of(Map.of( 99 | "_layer", "water" 100 | ), Map.of( 101 | "name", "waterway", 102 | "name:es", "waterway es", 103 | 104 | "_layer", "water_name", 105 | "_geom", 106 | new TestUtils.NormGeometry( 107 | GeoUtils.latLonToWorldCoords(GeoUtils.JTS_FACTORY.createGeometryCollection(new Geometry[]{ 108 | newLineString(0, 0, 1, 1), 109 | newLineString(2, 2, 3, 3) 110 | }))), 111 | "_minzoom", 3, 112 | "_maxzoom", 14, 113 | "_minpixelsize", "waterway".length() * 6d 114 | )), process(SimpleFeature.create( 115 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1E-7))), 116 | new HashMap<>(Map.of( 117 | "name", "waterway", 118 | "name:es", "waterway es", 119 | "natural", "water", 120 | "water", "pond" 121 | )), 122 | OpenMapTilesProfile.OSM_SOURCE, 123 | null, 124 | 10 125 | ))); 126 | } 127 | 128 | @Test 129 | void testWaterNameBaySmall() { 130 | assertFeatures(11, List.of(), process(SimpleFeature.create( 131 | newLineString(0, 0, 1, 1), 132 | new HashMap<>(Map.of( 133 | "OSM_ID", -10 134 | )), 135 | OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, 136 | null, 137 | 0 138 | ))); 139 | assertFeatures(10, List.of(Map.of( 140 | "name", "bay", 141 | "name:es", "bay es", 142 | 143 | "_layer", "water_name", 144 | "_type", "line", 145 | "_geom", new TestUtils.NormGeometry(GeoUtils.latLonToWorldCoords(newLineString(0, 0, 1, 1))), 146 | "_minzoom", 9, 147 | "_maxzoom", 14, 148 | "_minpixelsize", "bay".length() * 6d 149 | ), Map.of( 150 | "name", "bay", 151 | "name:es", "bay es", 152 | 153 | "_layer", "water_name", 154 | "_type", "point", 155 | "_minzoom", 3, 156 | "_maxzoom", 8, 157 | "_minpixelsize", 128d 158 | )), process(SimpleFeature.create( 159 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1E-7))), 160 | new HashMap<>(Map.of( 161 | "name", "bay", 162 | "name:es", "bay es", 163 | "natural", "bay" 164 | )), 165 | OpenMapTilesProfile.OSM_SOURCE, 166 | null, 167 | 10 168 | ))); 169 | } 170 | 171 | @Test 172 | void testWaterNameBayBig() { 173 | assertFeatures(11, List.of(), process(SimpleFeature.create( 174 | newLineString(0, 0, 1, 1), 175 | new HashMap<>(Map.of( 176 | "OSM_ID", -10 177 | )), 178 | OpenMapTilesProfile.LAKE_CENTERLINE_SOURCE, 179 | null, 180 | 0 181 | ))); 182 | assertFeatures(10, List.of(Map.of( 183 | "name", "bay", 184 | "name:es", "bay es", 185 | 186 | "_layer", "water_name", 187 | "_type", "line", 188 | "_minzoom", 9, 189 | "_maxzoom", 14 190 | ), Map.of( 191 | "name", "bay", 192 | "name:es", "bay es", 193 | 194 | "_layer", "water_name", 195 | "_type", "point", 196 | "_minzoom", 3, 197 | "_maxzoom", 8 198 | )), process(SimpleFeature.create( 199 | GeoUtils.worldToLatLonCoords(rectangle(0, Math.sqrt(1))), 200 | new HashMap<>(Map.of( 201 | "name", "bay", 202 | "name:es", "bay es", 203 | "natural", "bay" 204 | )), 205 | OpenMapTilesProfile.OSM_SOURCE, 206 | null, 207 | 10 208 | ))); 209 | } 210 | 211 | @Test 212 | void testMarinePoint() { 213 | assertFeatures(11, List.of(), process(SimpleFeature.create( 214 | newLineString(0, 0, 1, 1), 215 | new HashMap<>(Map.of( 216 | "scalerank", 1, 217 | "name", "Black sea" 218 | )), 219 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 220 | "ne_10m_geography_marine_polys", 221 | 0 222 | ))); 223 | 224 | // name match - use scale rank from NE 225 | assertFeatures(10, List.of(Map.of( 226 | "name", "Black Sea", 227 | "name:es", "Mar Negro", 228 | "_layer", "water_name", 229 | "_type", "point", 230 | "_minzoom", 1, 231 | "_maxzoom", 14 232 | )), process(pointFeature(Map.of( 233 | "rank", 9, 234 | "name", "Black Sea", 235 | "name:es", "Mar Negro", 236 | "place", "sea" 237 | )))); 238 | 239 | // name match but ocean - use min zoom=0 240 | assertFeatures(10, List.of(Map.of( 241 | "_layer", "water_name", 242 | "_type", "point", 243 | "_minzoom", 0, 244 | "_maxzoom", 14 245 | )), process(pointFeature(Map.of( 246 | "rank", 9, 247 | "name", "Black Sea", 248 | "place", "ocean" 249 | )))); 250 | 251 | // no name match - use OSM rank 252 | assertFeatures(10, List.of(Map.of( 253 | "_layer", "water_name", 254 | "_type", "point", 255 | "_minzoom", 9, 256 | "_maxzoom", 14 257 | )), process(pointFeature(Map.of( 258 | "rank", 9, 259 | "name", "Atlantic", 260 | "place", "sea" 261 | )))); 262 | 263 | // no rank at all, default to 8 264 | assertFeatures(10, List.of(Map.of( 265 | "_layer", "water_name", 266 | "_type", "point", 267 | "_minzoom", 8, 268 | "_maxzoom", 14 269 | )), process(pointFeature(Map.of( 270 | "name", "Atlantic", 271 | "place", "sea" 272 | )))); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/layers/WaterwayTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.layers; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.newLineString; 4 | import static org.junit.jupiter.api.Assertions.assertEquals; 5 | 6 | import com.onthegomap.planetiler.FeatureCollector; 7 | import com.onthegomap.planetiler.VectorTile; 8 | import com.onthegomap.planetiler.geo.GeometryException; 9 | import com.onthegomap.planetiler.reader.SimpleFeature; 10 | import com.onthegomap.planetiler.reader.osm.OsmElement; 11 | import com.onthegomap.planetiler.reader.osm.OsmReader; 12 | import com.onthegomap.planetiler.reader.osm.OsmRelationInfo; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Map; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.params.ParameterizedTest; 18 | import org.junit.jupiter.params.provider.ValueSource; 19 | import org.openmaptiles.OpenMapTilesProfile; 20 | 21 | class WaterwayTest extends AbstractLayerTest { 22 | 23 | @ParameterizedTest 24 | @ValueSource(booleans = {false, true}) 25 | void testOsmWaterwayRelation(boolean isLongEnough) throws GeometryException { 26 | var rel = new OsmElement.Relation(1); 27 | rel.setTag("name", "River Relation"); 28 | rel.setTag("name:es", "ES name"); 29 | rel.setTag("waterway", "river"); 30 | 31 | List relationInfos = profile.preprocessOsmRelation(rel); 32 | FeatureCollector features = process(SimpleFeature.createFakeOsmFeature( 33 | newLineString(0, 0, 0, isLongEnough ? 3 : 1), 34 | Map.of(), 35 | OpenMapTilesProfile.OSM_SOURCE, 36 | null, 37 | 0, 38 | (relationInfos == null ? List.of() : relationInfos).stream() 39 | .map(r -> new OsmReader.RelationMember<>("", r)).toList() 40 | )); 41 | assertFeatures(14, List.of(Map.of( 42 | "class", "river", 43 | "name", "River Relation", 44 | "name:es", "ES name", 45 | "_relid", 1L, 46 | 47 | "_layer", "waterway", 48 | "_type", "line", 49 | "_minzoom", 6, 50 | "_maxzoom", 8, 51 | "_buffer", 4d 52 | )), features); 53 | 54 | // ensure that post-processing combines waterways, and filters out ones that 55 | // belong to rivers that are not long enough to be shown 56 | var line1 = new VectorTile.Feature( 57 | Waterway.LAYER_NAME, 58 | 1, 59 | VectorTile.encodeGeometry(newLineString(0, 0, 10, 0)), 60 | mapOf("name", "river", "_relid", 1L), 61 | 0 62 | ); 63 | var line2 = new VectorTile.Feature( 64 | Waterway.LAYER_NAME, 65 | 1, 66 | VectorTile.encodeGeometry(newLineString(10, 0, 20, 0)), 67 | mapOf("name", "river", "_relid", 1L), 68 | 0 69 | ); 70 | var connected = new VectorTile.Feature( 71 | Waterway.LAYER_NAME, 72 | 1, 73 | VectorTile.encodeGeometry(newLineString(0, 0, 20, 0)), 74 | mapOf("name", "river"), 75 | 0 76 | ); 77 | 78 | assertEquals( 79 | isLongEnough ? List.of(connected) : List.of(), 80 | profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 8, new ArrayList<>(List.of(line1, line2))) 81 | ); 82 | } 83 | 84 | @Test 85 | void testWaterwayImportantRiverProcess() { 86 | var charlesRiver = process(lineFeature(Map.of( 87 | "waterway", "river", 88 | "name", "charles river", 89 | "name:es", "es name" 90 | ))); 91 | assertFeatures(14, List.of(Map.of( 92 | "class", "river", 93 | "name", "charles river", 94 | "name:es", "es name", 95 | "intermittent", 0, 96 | 97 | "_layer", "waterway", 98 | "_type", "line", 99 | "_minzoom", 9, 100 | "_maxzoom", 14, 101 | "_buffer", 4d 102 | )), charlesRiver); 103 | assertFeatures(11, List.of(Map.of( 104 | "class", "river", 105 | "name", "charles river", 106 | "name:es", "es name", 107 | "intermittent", "", 108 | "_buffer", 13.082664546679323 109 | )), charlesRiver); 110 | assertFeatures(10, List.of(Map.of( 111 | "class", "river", 112 | "_buffer", 26.165329093358647 113 | )), charlesRiver); 114 | assertFeatures(9, List.of(Map.of( 115 | "class", "river", 116 | "_buffer", 26.165329093358647 117 | )), charlesRiver); 118 | } 119 | 120 | @Test 121 | void testWaterwayImportantRiverPostProcess() throws GeometryException { 122 | var line1 = new VectorTile.Feature( 123 | Waterway.LAYER_NAME, 124 | 1, 125 | VectorTile.encodeGeometry(newLineString(0, 0, 10, 0)), 126 | Map.of("name", "river"), 127 | 0 128 | ); 129 | var line2 = new VectorTile.Feature( 130 | Waterway.LAYER_NAME, 131 | 1, 132 | VectorTile.encodeGeometry(newLineString(10, 0, 20, 0)), 133 | Map.of("name", "river"), 134 | 0 135 | ); 136 | var connected = new VectorTile.Feature( 137 | Waterway.LAYER_NAME, 138 | 1, 139 | VectorTile.encodeGeometry(newLineString(0, 0, 20, 0)), 140 | Map.of("name", "river"), 141 | 0 142 | ); 143 | 144 | assertEquals( 145 | List.of(), 146 | profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 11, List.of()) 147 | ); 148 | assertEquals( 149 | List.of(line1, line2), 150 | profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 12, List.of(line1, line2)) 151 | ); 152 | assertEquals( 153 | List.of(connected), 154 | profile.postProcessLayerFeatures(Waterway.LAYER_NAME, 11, List.of(line1, line2)) 155 | ); 156 | } 157 | 158 | @Test 159 | void testWaterwaySmaller() { 160 | // river with no name is not important 161 | assertFeatures(14, List.of(Map.of( 162 | "class", "river", 163 | "brunnel", "bridge", 164 | 165 | "_layer", "waterway", 166 | "_type", "line", 167 | "_minzoom", 12 168 | )), process(lineFeature(Map.of( 169 | "waterway", "river", 170 | "bridge", "1" 171 | )))); 172 | 173 | assertFeatures(14, List.of(Map.of( 174 | "class", "canal", 175 | "_layer", "waterway", 176 | "_type", "line", 177 | "_minzoom", 12 178 | )), process(lineFeature(Map.of( 179 | "waterway", "canal", 180 | "name", "name" 181 | )))); 182 | 183 | assertFeatures(14, List.of(Map.of( 184 | "class", "stream", 185 | "_layer", "waterway", 186 | "_type", "line", 187 | "_minzoom", 13 188 | )), process(lineFeature(Map.of( 189 | "waterway", "stream", 190 | "name", "name" 191 | )))); 192 | } 193 | 194 | @Test 195 | void testWaterwayNaturalEarth() { 196 | assertFeatures(3, List.of(Map.of( 197 | "class", "river", 198 | "name", "", 199 | "intermittent", "", 200 | 201 | "_layer", "waterway", 202 | "_type", "line", 203 | "_minzoom", 3, 204 | "_maxzoom", 3 205 | )), process(SimpleFeature.create( 206 | newLineString(0, 0, 1, 1), 207 | Map.of( 208 | "featurecla", "River", 209 | "name", "name" 210 | ), 211 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 212 | "ne_110m_rivers_lake_centerlines", 213 | 0 214 | ))); 215 | 216 | assertFeatures(6, List.of(Map.of( 217 | "class", "river", 218 | "intermittent", "", 219 | 220 | "_layer", "waterway", 221 | "_type", "line", 222 | "_minzoom", 4, 223 | "_maxzoom", 5 224 | )), process(SimpleFeature.create( 225 | newLineString(0, 0, 1, 1), 226 | Map.of( 227 | "featurecla", "River", 228 | "name", "name" 229 | ), 230 | OpenMapTilesProfile.NATURAL_EARTH_SOURCE, 231 | "ne_50m_rivers_lake_centerlines", 232 | 0 233 | ))); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/util/OmtLanguageUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.util; 2 | 3 | import static com.onthegomap.planetiler.TestUtils.assertSubmap; 4 | import static com.onthegomap.planetiler.util.LanguageUtils.containsOnlyLatinCharacters; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertFalse; 7 | import static org.junit.jupiter.api.Assertions.assertNull; 8 | 9 | import com.onthegomap.planetiler.util.Translations; 10 | import com.onthegomap.planetiler.util.Wikidata; 11 | import java.util.List; 12 | import java.util.Map; 13 | import org.junit.jupiter.api.Test; 14 | import org.junit.jupiter.params.ParameterizedTest; 15 | import org.junit.jupiter.params.provider.CsvSource; 16 | import org.junit.jupiter.params.provider.ValueSource; 17 | 18 | class OmtLanguageUtilsTest { 19 | 20 | private final Wikidata.WikidataTranslations wikidataTranslations = new Wikidata.WikidataTranslations(); 21 | private final Translations translations = Translations.defaultProvider(List.of("en", "es", "de")) 22 | .addFallbackTranslationProvider(wikidataTranslations); 23 | 24 | @Test 25 | void testSimpleExample() { 26 | assertSubmap(Map.of( 27 | "name", "name", 28 | "name_en", "english name", 29 | "name_de", "german name" 30 | ), OmtLanguageUtils.getNames(Map.of( 31 | "name", "name", 32 | "name:en", "english name", 33 | "name:de", "german name" 34 | ), translations)); 35 | 36 | assertSubmap(Map.of( 37 | "name", "name", 38 | "name_en", "name", 39 | "name_de", "german name" 40 | ), OmtLanguageUtils.getNames(Map.of( 41 | "name", "name", 42 | "name:de", "german name" 43 | ), translations)); 44 | 45 | assertSubmap(Map.of( 46 | "name", "name", 47 | "name_en", "english name", 48 | "name_de", "name" 49 | ), OmtLanguageUtils.getNames(Map.of( 50 | "name", "name", 51 | "name:en", "english name" 52 | ), translations)); 53 | } 54 | 55 | @ParameterizedTest 56 | @CsvSource({ 57 | "abc, true", 58 | "5!, true", 59 | "5~, true", 60 | "é, true", 61 | "éś, true", 62 | "ɏə, true", 63 | "ɐ, true", 64 | "ᵿἀ, false", 65 | "Ḁỿ, true", 66 | "\u02ff\u0370, false", 67 | "\u0030\u036f, true", 68 | "日本, false", 69 | "abc本123, false", 70 | }) 71 | void testIsLatin(String in, boolean isLatin) { 72 | if (!isLatin) { 73 | assertFalse(containsOnlyLatinCharacters(in)); 74 | } else { 75 | assertEquals(in, OmtLanguageUtils.getNames(Map.of( 76 | "name", in 77 | ), translations).get("name:latin")); 78 | } 79 | } 80 | 81 | 82 | @ParameterizedTest 83 | @CsvSource(value = { 84 | "abcaāíìś+, null", 85 | "abca日āíìś+, 日+", 86 | "(abc), null", 87 | "日本 (Japan), 日本", 88 | "日本 [Japan - Nippon], 日本", 89 | " Japan - Nippon (Japan) - Japan - 日本 - Japan - Nippon (Japan), 日本", 90 | "Japan - 日本~+ , 日本~+", 91 | "Japan / 日本 / Japan , 日本", 92 | }, nullValues = "null") 93 | void testRemoveNonLatin(String in, String out) { 94 | assertEquals(out, OmtLanguageUtils.getNames(Map.of( 95 | "name", in 96 | ), translations).get("name:nonlatin")); 97 | } 98 | 99 | @ParameterizedTest 100 | @ValueSource(strings = { 101 | // OSM tags that SHOULD be eligible for name:latin feature in the output 102 | "name:en", 103 | "name:en-US", 104 | "name:en-010", 105 | "int_name", 106 | "name:fr", 107 | "name:es", 108 | "name:pt", 109 | "name:de", 110 | "name:ar", 111 | "name:it", 112 | "name:ko-Latn", 113 | "name:be-tarask", 114 | // https://wiki.openstreetmap.org/wiki/Multilingual_names#Japan 115 | "name:ja", 116 | "name:ja-Latn", 117 | "name:ja_rm", 118 | "name:ja_kana", 119 | // https://wiki.openstreetmap.org/wiki/Multilingual_names#China 120 | "name:zh-CN", 121 | "name:zh-hant-CN", 122 | "name:zh_pinyin", 123 | "name:zh_zhuyin", 124 | "name:zh-Latn-tongyong", 125 | "name:zh-Latn-pinyin", 126 | "name:zh-Latn-wadegiles", 127 | "name:yue-Latn-jyutping", 128 | // https://wiki.openstreetmap.org/wiki/Multilingual_names#France 129 | "name:fr", 130 | "name:fr-x-gallo", 131 | "name:br", 132 | "name:oc", 133 | "name:vls", 134 | "name:frp", 135 | "name:gcf", 136 | "name:gsw", 137 | }) 138 | void testLatinFallbacks(String key) { 139 | assertEquals("a", OmtLanguageUtils.getNames(Map.of( 140 | key, "a" 141 | ), translations).get("name:latin")); 142 | assertNull(OmtLanguageUtils.getNames(Map.of( 143 | key, "ア" 144 | ), translations).get("name:latin")); 145 | assertNull(OmtLanguageUtils.getNames(Map.of( 146 | key, "غ" 147 | ), translations).get("name:latin")); 148 | } 149 | 150 | @ParameterizedTest 151 | @ValueSource(strings = { 152 | // OSM tags that should NOT be eligible for name:latin feature in the output 153 | "name:signed", 154 | "name:prefix", 155 | "name:abbreviation", 156 | "name:source", 157 | "name:full", 158 | "name:adjective", 159 | "name:proposed", 160 | "name:pronunciation", 161 | "name:etymology", 162 | "name:etymology:wikidata", 163 | "name:etymology:wikipedia", 164 | "name:etymology:right", 165 | "name:etymology:left", 166 | "name:genitive", 167 | }) 168 | void testNoLatinFallback(String key) { 169 | assertSubmap(Map.of( 170 | "name", "Branch Hill–Loveland Road", 171 | "name_en", "Branch Hill–Loveland Road", 172 | "name_de", "Branch Hill–Loveland Road", 173 | "name:latin", "Branch Hill–Loveland Road", 174 | "name_int", "Branch Hill–Loveland Road" 175 | ), OmtLanguageUtils.getNames(Map.of( 176 | "name", "Branch Hill–Loveland Road", 177 | key, "Q22133584;Q843993" 178 | ), translations)); 179 | assertSubmap(Map.of( 180 | "name", "日", 181 | "name_en", "日", 182 | "name_de", "日", 183 | "name:latin", "rì", 184 | "name_int", "rì" 185 | ), OmtLanguageUtils.getNames(Map.of( 186 | "name", "日", 187 | key, "other" // don't use this latin string with invalid name keys 188 | ), translations)); 189 | } 190 | 191 | @ParameterizedTest 192 | @CsvSource({ 193 | "キャンパス, kyanpasu", 194 | "Αλφαβητικός Κατάλογος, Alphabētikós Katálogos", 195 | "биологическом, biologičeskom", 196 | }) 197 | void testTransliterate(String in, String out) { 198 | assertEquals(out, OmtLanguageUtils.getNames(Map.of( 199 | "name", in 200 | ), translations).get("name:latin")); 201 | translations.setShouldTransliterate(false); 202 | assertNull(OmtLanguageUtils.getNames(Map.of( 203 | "name", in 204 | ), translations).get("name:latin")); 205 | } 206 | 207 | @Test 208 | void testUseWikidata() { 209 | wikidataTranslations.put(123, "es", "es name"); 210 | assertSubmap(Map.of( 211 | "name:es", "es name" 212 | ), OmtLanguageUtils.getNames(Map.of( 213 | "name", "name", 214 | "wikidata", "Q123" 215 | ), translations)); 216 | } 217 | 218 | @Test 219 | void testUseOsm() { 220 | assertSubmap(Map.of( 221 | "name:es", "es name osm" 222 | ), OmtLanguageUtils.getNames(Map.of( 223 | "name", "name", 224 | "wikidata", "Q123", 225 | "name:es", "es name osm" 226 | ), translations)); 227 | } 228 | 229 | @Test 230 | void testPreferOsm() { 231 | wikidataTranslations.put(123, "es", "wd es name"); 232 | wikidataTranslations.put(123, "de", "wd de name"); 233 | assertSubmap(Map.of( 234 | "name:es", "wd es name", 235 | "name:de", "de name osm" 236 | ), OmtLanguageUtils.getNames(Map.of( 237 | "name", "name", 238 | "wikidata", "Q123", 239 | "name:de", "de name osm" 240 | ), translations)); 241 | } 242 | 243 | @Test 244 | void testDontUseTranslationsWhenNotSpecified() { 245 | var result = OmtLanguageUtils.getNamesWithoutTranslations(Map.of( 246 | "name", "name", 247 | "wikidata", "Q123", 248 | "name:es", "es name osm", 249 | "name:de", "de name osm" 250 | )); 251 | assertNull(result.get("name:es")); 252 | assertNull(result.get("name:de")); 253 | assertEquals("name", result.get("name")); 254 | } 255 | 256 | @Test 257 | void testIgnoreLanguages() { 258 | wikidataTranslations.put(123, "ja", "ja name wd"); 259 | var result = OmtLanguageUtils.getNamesWithoutTranslations(Map.of( 260 | "name", "name", 261 | "wikidata", "Q123", 262 | "name:ja", "ja name osm" 263 | )); 264 | assertNull(result.get("name:ja")); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/test/java/org/openmaptiles/util/VerifyMonacoTest.java: -------------------------------------------------------------------------------- 1 | package org.openmaptiles.util; 2 | 3 | import static com.onthegomap.planetiler.geo.GeoUtils.point; 4 | import static com.onthegomap.planetiler.util.Gzip.gzip; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import com.onthegomap.planetiler.VectorTile; 8 | import com.onthegomap.planetiler.archive.TileEncodingResult; 9 | import com.onthegomap.planetiler.geo.TileCoord; 10 | import com.onthegomap.planetiler.mbtiles.Mbtiles; 11 | import java.io.IOException; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.OptionalLong; 15 | import org.junit.jupiter.api.AfterEach; 16 | import org.junit.jupiter.api.BeforeEach; 17 | import org.junit.jupiter.api.Test; 18 | 19 | class VerifyMonacoTest { 20 | 21 | private Mbtiles mbtiles; 22 | 23 | @BeforeEach 24 | public void setup() { 25 | mbtiles = Mbtiles.newInMemoryDatabase(); 26 | } 27 | 28 | @AfterEach 29 | public void teardown() throws IOException { 30 | mbtiles.close(); 31 | } 32 | 33 | @Test 34 | void testEmptyFileInvalid() { 35 | assertInvalid(mbtiles); 36 | } 37 | 38 | @Test 39 | void testEmptyTablesInvalid() { 40 | mbtiles.createTablesWithIndexes(); 41 | assertInvalid(mbtiles); 42 | } 43 | 44 | @Test 45 | void testStillInvalidWithOneTile() throws IOException { 46 | mbtiles.createTablesWithIndexes(); 47 | mbtiles.metadataTable().setMetadata("name", "name"); 48 | try (var writer = mbtiles.newBatchedTileWriter()) { 49 | VectorTile tile = new VectorTile(); 50 | tile.addLayerFeatures("layer", List.of(new VectorTile.Feature( 51 | "layer", 52 | 1, 53 | VectorTile.encodeGeometry(point(0, 0)), 54 | Map.of() 55 | ))); 56 | writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 0), gzip(tile.encode()), OptionalLong.empty())); 57 | } 58 | assertInvalid(mbtiles); 59 | } 60 | 61 | private void assertInvalid(Mbtiles mbtiles) { 62 | assertTrue(VerifyMonaco.verify(mbtiles).numErrors() > 0); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /submodule.pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 4.0.0 7 | 8 | org.openmaptiles 9 | planetiler-openmaptiles 10 | 11 | 12 | com.onthegomap.planetiler 13 | planetiler-parent 14 | ${revision} 15 | 16 | 17 | 18 | 19 | com.onthegomap.planetiler 20 | planetiler-core 21 | ${project.version} 22 | 23 | 24 | org.yaml 25 | snakeyaml 26 | 1.33 27 | 28 | 29 | org.commonmark 30 | commonmark 31 | 32 | 33 | 34 | com.onthegomap.planetiler 35 | planetiler-core 36 | ${project.version} 37 | test-jar 38 | test 39 | 40 | 41 | 42 | 43 | 44 | 45 | io.github.zlika 46 | reproducible-build-maven-plugin 47 | 48 | 49 | 50 | maven-deploy-plugin 51 | 52 | 53 | true 54 | 55 | 56 | 57 | 58 | 59 | --------------------------------------------------------------------------------